You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2018/12/14 10:15:24 UTC

[1/3] ignite git commit: IGNITE-9777: Implemented IO statistics for logic and physical page reads. This closes #4934.

Repository: ignite
Updated Branches:
  refs/heads/master 2a0f3dfaf -> 1eb575026


http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsManager.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsManager.java b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsManager.java
new file mode 100644
index 0000000..a4be159
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsManager.java
@@ -0,0 +1,204 @@
+/*
+ * 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.stat;
+
+import java.time.OffsetDateTime;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteIllegalStateException;
+
+/**
+ * IO statistics manager to manage of gathering IO statistics.
+ */
+public class IoStatisticsManager {
+    /** All statistic holders */
+    private final Map<IoStatisticsType, Map<IoStatisticsHolderKey, IoStatisticsHolder>> statByType;
+
+    /** Time of since statistics start gathering. */
+    private volatile OffsetDateTime startTime;
+
+    /**
+     * Constructor.
+     */
+    public IoStatisticsManager() {
+        statByType = new EnumMap<>(IoStatisticsType.class);
+
+        for (IoStatisticsType types : IoStatisticsType.values())
+            statByType.put(types, new ConcurrentHashMap<>());
+
+        reset();
+    }
+
+    /**
+     * Create and register statistics holder.
+     *
+     * @param type Type of statistics.
+     * @param name Name of element of statistics.
+     * @return created statistics holder.
+     */
+    public IoStatisticsHolder register(IoStatisticsType type, String name) {
+        return register(type, name, null);
+    }
+
+    /**
+     * Create and register statistics holder.
+     *
+     * @param type Type of statistics.
+     * @param name Name of element of statistics.
+     * @param subName Subname of element of statistics.
+     * @return created statistics holder.
+     */
+    public IoStatisticsHolder register(IoStatisticsType type, String name, String subName) {
+        if (statByType.isEmpty())
+            throw new IgniteIllegalStateException("IO Statistics manager has been stopped and can'be used");
+
+        IoStatisticsHolder stat;
+        IoStatisticsHolderKey statKey;
+
+        switch (type) {
+            case CACHE_GROUP:
+                stat = new IoStatisticsHolderCache(name);
+                statKey = new IoStatisticsHolderKey(name);
+
+                break;
+
+            case HASH_INDEX:
+            case SORTED_INDEX:
+                stat = new IoStatisticsHolderIndex(name, subName);
+                statKey = new IoStatisticsHolderKey(name, subName);
+
+                break;
+
+            default:
+                throw new IgniteException("Gathering IO statistics for " + type + "doesn't support");
+        }
+
+        IoStatisticsHolder existedStatisitcHolder = statByType.get(type).putIfAbsent(statKey, stat);
+
+        return (existedStatisitcHolder != null) ? existedStatisitcHolder : stat;
+    }
+
+    /**
+     * Remove all holders.
+     */
+    public void stop() {
+        statByType.clear();
+    }
+
+    /**
+     * Reset statistics
+     */
+    public void reset() {
+        statByType.forEach((t, s) ->
+            s.forEach((k, sh) -> sh.resetStatistics())
+        );
+
+        startTime = OffsetDateTime.now();
+    }
+
+    /**
+     * @return When statistics gathering start.
+     */
+    public OffsetDateTime startTime() {
+        return startTime;
+    }
+
+    /**
+     * Extract all tracked names for given statistics type.
+     *
+     * @param statType Type of statistics which tracked names need to extract.
+     * @return Set of present names for given statType
+     */
+    public Set<String> deriveStatisticNames(IoStatisticsType statType) {
+        assert statType != null;
+
+        return statByType.get(statType).keySet().stream()
+            .map(IoStatisticsHolderKey::name)
+            .collect(Collectors.toSet());
+    }
+
+    /**
+     * Extract all tracked subNames for given statistics type .
+     *
+     * @param name Name of element of statistics.
+     * @param statType Type of statistics which tracked names need to extract.
+     * @return Set of present names for given statType
+     */
+    public Set<String> deriveStatisticSubNames(IoStatisticsType statType, String name) {
+        assert statType != null;
+
+        return statByType.get(statType).keySet().stream()
+            .filter(k -> k.name().equalsIgnoreCase(name) && k.subName() != null)
+            .map(IoStatisticsHolderKey::subName)
+            .collect(Collectors.toSet());
+    }
+
+    /**
+     * @param statType Type of statistics which need to take.
+     * @param name name of statistics which need to take, e.g. cache name
+     * @param subName subName of statistics which need to take, e.g. index name.
+     * @return Tracked physical reads by types since last reset statistics.
+     */
+    public Map<String, Long> physicalReadsMap(IoStatisticsType statType, String name, String subName) {
+        IoStatisticsHolder statHolder = statByType.get(statType).get(new IoStatisticsHolderKey(name, subName));
+
+        return (statHolder != null) ? statHolder.physicalReadsMap() : Collections.emptyMap();
+    }
+
+    /**
+     * @param statType Type of statistics which need to take.
+     * @param name name of statistics which need to take, e.g. cache name
+     * @param subName subName of statistics which need to take, e.g. index name.
+     * @return Number of physical reads since last reset statistics.
+     */
+    public Long physicalReads(IoStatisticsType statType, String name, String subName) {
+        IoStatisticsHolder statHolder = statByType.get(statType).get(new IoStatisticsHolderKey(name, subName));
+
+        return (statHolder != null) ? statHolder.physicalReads() : null;
+    }
+
+    /**
+     * @param statType Type of statistics which need to take.
+     * @param name name of statistics which need to take, e.g. cache name
+     * @param subName subName of statistics which need to take, e.g. index name.
+     * @return Tracked logical reads by types since last reset statistics.
+     */
+    public Map<String, Long> logicalReadsMap(IoStatisticsType statType, String name, String subName) {
+        IoStatisticsHolder statHolder = statByType.get(statType).get(new IoStatisticsHolderKey(name, subName));
+
+        return (statHolder != null) ? statHolder.logicalReadsMap() : Collections.emptyMap();
+    }
+
+    /**
+     * @param statType Type of statistics which need to take.
+     * @param name name of statistics which need to take, e.g. cache name
+     * @param subName subName of statistics which need to take, e.g. index name.
+     * @return Number of logical reads since last reset statistics.
+     */
+    public Long logicalReads(IoStatisticsType statType, String name, String subName) {
+        IoStatisticsHolder stat = statByType.get(statType).get(new IoStatisticsHolderKey(name, subName));
+
+        return (stat != null) ? stat.logicalReads() : null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsMetricsLocalMXBeanImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsMetricsLocalMXBeanImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsMetricsLocalMXBeanImpl.java
new file mode 100644
index 0000000..8248d34
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsMetricsLocalMXBeanImpl.java
@@ -0,0 +1,146 @@
+/*
+ * 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.stat;
+
+import java.time.format.DateTimeFormatter;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.apache.ignite.mxbean.IoStatisticsMetricsMXBean;
+
+import static org.apache.ignite.internal.stat.IoStatisticsHolderIndex.HASH_PK_IDX_NAME;
+
+/**
+ * JMX bean to expose local node IO statistics.
+ */
+public class IoStatisticsMetricsLocalMXBeanImpl implements IoStatisticsMetricsMXBean {
+    /** IO statistic manager. */
+    private IoStatisticsManager statMgr;
+
+    /**
+     * @param statMgr IO statistic manager.
+     */
+    public IoStatisticsMetricsLocalMXBeanImpl(IoStatisticsManager statMgr) {
+        this.statMgr = statMgr;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long getStartTime() {
+        return statMgr.startTime().toEpochSecond();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getStartTimeLocal() {
+        return statMgr.startTime().format(DateTimeFormatter.ISO_DATE_TIME);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void reset() {
+        statMgr.reset();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getCacheGroupStatistics(String cacheGrpName) {
+        return formattedStats(IoStatisticsType.CACHE_GROUP, cacheGrpName, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Long getCacheGroupPhysicalReads(String cacheGrpName) {
+        return statMgr.physicalReads(IoStatisticsType.CACHE_GROUP, cacheGrpName, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Long getCacheGroupLogicalReads(String cacheGrpName) {
+        return statMgr.logicalReads(IoStatisticsType.CACHE_GROUP, cacheGrpName, null);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String getIndexStatistics(String cacheGrpName, String idxName) {
+        return formattedStats(getIndexStatType(idxName), cacheGrpName, idxName);
+    }
+
+    /**
+     * @param idxName Name of index
+     * @return Type of index statistics.
+     */
+    private IoStatisticsType getIndexStatType(String idxName) {
+        return idxName.equals(HASH_PK_IDX_NAME) ? IoStatisticsType.HASH_INDEX : IoStatisticsType.SORTED_INDEX;
+    }
+
+    /**
+     * Gets string presentation of IO statistics for given parameters.
+     *
+     * @param statType Type of statistics.
+     * @param name Name of statistics
+     * @param subName SubName of statistics.
+     * @return String presentation of IO statistics for given parameters.
+     */
+    private String formattedStats(IoStatisticsType statType, String name, String subName) {
+        Map<String, Long> logicalReads = statMgr.logicalReadsMap(statType, name, subName);
+
+        Map<String, Long> physicalReads = statMgr.physicalReadsMap(statType, name, subName);
+
+        String stats = Stream.concat(logicalReads.entrySet().stream(), physicalReads.entrySet().stream())
+            .map(e -> e.getKey() + "=" + e.getValue())
+            .collect(Collectors.joining(", ", "[", "]"));
+
+        String statInfo = statType.name() + " " + (subName != null ? name + "." + subName : name);
+
+        return statInfo + " " + stats;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Long getIndexPhysicalReads(String cacheGrpName, String idxName) {
+        return statMgr.physicalReads(getIndexStatType(idxName), cacheGrpName, idxName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Long getIndexLogicalReads(String cacheGrpName, String idxName) {
+        return statMgr.logicalReads(getIndexStatType(idxName), cacheGrpName, idxName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Long getIndexLeafLogicalReads(String cacheGrpName, String idxName) {
+        Map<String, Long> logicalReads = statMgr.logicalReadsMap(getIndexStatType(idxName), cacheGrpName, idxName);
+
+        return logicalReads.get(IoStatisticsHolderIndex.LOGICAL_READS_LEAF);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Long getIndexLeafPhysicalReads(String cacheGrpName, String idxName) {
+        Map<String, Long> logicalReads = statMgr.physicalReadsMap(getIndexStatType(idxName), cacheGrpName, idxName);
+
+        return logicalReads.get(IoStatisticsHolderIndex.PHYSICAL_READS_LEAF);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Long getIndexInnerLogicalReads(String cacheGrpName, String idxName) {
+        Map<String, Long> logicalReads = statMgr.logicalReadsMap(getIndexStatType(idxName), cacheGrpName, idxName);
+
+        return logicalReads.get(IoStatisticsHolderIndex.LOGICAL_READS_INNER);
+    }
+
+    /** {@inheritDoc} */
+    @Override public Long getIndexInnerPhysicalReads(String cacheGrpName, String idxName) {
+        Map<String, Long> logicalReads = statMgr.physicalReadsMap(getIndexStatType(idxName), cacheGrpName, idxName);
+
+        return logicalReads.get(IoStatisticsHolderIndex.PHYSICAL_READS_INNER);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsQueryHelper.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsQueryHelper.java b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsQueryHelper.java
new file mode 100644
index 0000000..399aca2
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsQueryHelper.java
@@ -0,0 +1,98 @@
+/*
+ * 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.stat;
+
+/**
+ * Helper for gathering IO statistics.
+ */
+public class IoStatisticsQueryHelper {
+    /** */
+    private static final ThreadLocal<IoStatisticsHolderQuery> CUR_QRY_STATS = new ThreadLocal<>();
+
+    /**
+     * Start gathering IO statistics for query. Should be used together with {@code finishGatheringQueryStatistics}
+     * method.
+     *
+     * @param qryId Identifier of query.
+     */
+    public static void startGatheringQueryStatistics(String qryId) {
+        IoStatisticsHolderQuery currQryStatisticsHolder = CUR_QRY_STATS.get();
+
+        assert currQryStatisticsHolder == null : currQryStatisticsHolder;
+
+        CUR_QRY_STATS.set(new IoStatisticsHolderQuery(qryId));
+    }
+
+    /**
+     * Merge query statistics.
+     *
+     * @param qryStat Statistics which will be merged to current query statistics.
+     */
+    public static void mergeQueryStatistics(IoStatisticsHolderQuery qryStat) {
+        assert qryStat != null;
+
+        IoStatisticsHolderQuery currQryStatisticsHolder = CUR_QRY_STATS.get();
+
+        assert currQryStatisticsHolder != null;
+
+        currQryStatisticsHolder.merge(qryStat.logicalReads(), qryStat.physicalReads());
+    }
+
+    /**
+     * Finish gathering IO statistics for query. Should be used together with {@code startGatheringQueryStatistics}
+     * method.
+     *
+     * @return Gathered statistics.
+     */
+    public static IoStatisticsHolder finishGatheringQueryStatistics() {
+        IoStatisticsHolderQuery currQryStatisticsHolder = CUR_QRY_STATS.get();
+
+        assert currQryStatisticsHolder != null;
+
+        CUR_QRY_STATS.remove();
+
+        return currQryStatisticsHolder;
+    }
+
+    /**
+     * Track logical read for current query.
+     *
+     * @param pageAddr Address of page.
+     */
+    static void trackLogicalReadQuery(long pageAddr) {
+        IoStatisticsHolderQuery currQryStatisticsHolder = CUR_QRY_STATS.get();
+
+        if (currQryStatisticsHolder != null)
+            currQryStatisticsHolder.trackLogicalRead(pageAddr);
+
+    }
+
+    /**
+     * Track physical and logical read for current query.
+     *
+     * @param pageAddr Address of page.
+     */
+    static void trackPhysicalAndLogicalReadQuery(long pageAddr) {
+        IoStatisticsHolderQuery currQryStatisticsHolder = CUR_QRY_STATS.get();
+
+        if (currQryStatisticsHolder != null)
+            currQryStatisticsHolder.trackPhysicalAndLogicalRead(pageAddr);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsType.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsType.java b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsType.java
new file mode 100644
index 0000000..67577a4
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsType.java
@@ -0,0 +1,36 @@
+/*
+ * 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.stat;
+
+/**
+ * Type of statistics.
+ */
+public enum IoStatisticsType {
+    /** Cache group. */
+    CACHE_GROUP,
+
+    /** Hash index. */
+    HASH_INDEX,
+
+    /** Sorted index. */
+    SORTED_INDEX,
+
+    /** SQL. */
+    SQL
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/mxbean/IoStatisticsMetricsMXBean.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/mxbean/IoStatisticsMetricsMXBean.java b/modules/core/src/main/java/org/apache/ignite/mxbean/IoStatisticsMetricsMXBean.java
new file mode 100644
index 0000000..bad848c
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/mxbean/IoStatisticsMetricsMXBean.java
@@ -0,0 +1,172 @@
+/*
+ * 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.mxbean;
+
+/**
+ * This interface defines JMX view for IO statistics.
+ */
+@MXBeanDescription("MBean that provides access IO statistics metrics.")
+public interface IoStatisticsMetricsMXBean {
+
+    /**
+     * @return Start time of gathering statistics as UTC milliseconds.
+     */
+    @MXBeanDescription("Start time of gathering staistics.")
+    long getStartTime();
+
+    /**
+     * @return Start time of gathering statistics in ISO-8601 format.
+     */
+    @MXBeanDescription("Start time of gathering staistics.")
+    String getStartTimeLocal();
+
+    /**
+     * Reset all IO statistics.
+     */
+    @MXBeanDescription("Reset gathered statistics.")
+    public void reset();
+
+    /**
+     * Gets string presentation of cache group IO statistics for given cache group.
+     *
+     * @param cacheGrpName Name of cache group.
+     * @return Formatted representation of cache group IO statistics.
+     */
+    @MXBeanDescription("String presentation of cache group IO statistics.")
+    @MXBeanParametersNames("cacheGrpName")
+    @MXBeanParametersDescriptions("Cache group name.")
+    public String getCacheGroupStatistics(String cacheGrpName);
+
+    /**
+     * Gets number of physical page reads for given cache group.
+     *
+     * @param cacheGrpName Name of cache group.
+     * @return Number of physical page reads for given cache group. {@code null} in case such statistics doesn't exists.
+     */
+    @MXBeanDescription("Number of physical page reads for given cache group." +
+        "Can return null in case such statistics doesn't exists.")
+    @MXBeanParametersNames("cacheGrpName")
+    @MXBeanParametersDescriptions("Cache group name.")
+    public Long getCacheGroupPhysicalReads(String cacheGrpName);
+
+    /**
+     * Gets number of logical page reads for given cache group.
+     *
+     * @param cacheGrpName Name of cache group.
+     * @return Number of logical page reads for given cache group. {@code null} in case such statistics doesn't exists.
+     */
+    @MXBeanDescription("Number of logical page reads for given cache group. " +
+        "Can return null in case such statistics doesn't exists.")
+    @MXBeanParametersNames("cacheGrpName")
+    @MXBeanParametersDescriptions("Cache group name.")
+    public Long getCacheGroupLogicalReads(String cacheGrpName);
+
+    /**
+     * Gets string presentation of index IO statistics for given cache group and index.
+     *
+     * @param cacheGrpName Name of cache group.
+     * @param idxName Name of index.
+     * @return Formatted representation of index IO statistics for given cache group and index.
+     */
+    @MXBeanDescription("String presentation of index IO statistics.")
+    @MXBeanParametersNames({"cacheGrpName", "idxName"})
+    @MXBeanParametersDescriptions({"Cache group name.", "Index name."})
+    public String getIndexStatistics(String cacheGrpName, String idxName);
+
+    /**
+     * Gets number of physical index page reads for given cache group and index.
+     *
+     * @param cacheGrpName Name of cache group.
+     * @param idxName Name of index.
+     * @return Number of physical page reads for given cache group and index. {@code null} in case such statistics doesn't exists.
+     */
+    @MXBeanDescription("Number of physical page reads for given cache group." +
+        "Can return null in case such statistics doesn't exists.")
+    @MXBeanParametersNames({"cacheGrpName", "idxName"})
+    @MXBeanParametersDescriptions({"Cache group name.", "Index name."})
+    public Long getIndexPhysicalReads(String cacheGrpName, String idxName);
+
+    /**
+     * Gets number of logical index page reads for given cache group and index.
+     *
+     * @param cacheGrpName Name of cache group.
+     * @param idxName Name of index.
+     * @return Number of logical page reads for given cache group and index. {@code null} in case such statistics doesn't exists.
+     */
+    @MXBeanDescription("Number of logical page reads for given cache group." +
+        "Can return null in case such statistics doesn't exists.")
+    @MXBeanParametersNames({"cacheGrpName", "idxName"})
+    @MXBeanParametersDescriptions({"Cache group name.", "Index name."})
+    public Long getIndexLogicalReads(String cacheGrpName, String idxName);
+
+
+    /**
+     * Gets number of logical leaf index's page reads for given cache group and index.
+     *
+     * @param cacheGrpName Name of cache group.
+     * @param idxName Name of index.
+     * @return Number of logical leaf index's page reads for given cache group and index. {@code null} in case such statistics doesn't exists.
+     */
+    @MXBeanDescription("Number of logical leaf index's page reads for given cache group and index." +
+        "Can return null in case such statistics doesn't exists.")
+    @MXBeanParametersNames({"cacheGrpName", "idxName"})
+    @MXBeanParametersDescriptions({"Cache group name.", "Index name."})
+    public Long getIndexLeafLogicalReads(String cacheGrpName, String idxName);
+
+    /**
+     * Gets number of physical leaf index's page reads for given cache group and index.
+     *
+     * @param cacheGrpName Name of cache group.
+     * @param idxName Name of index.
+     * @return Number of physical leaf index's page reads for given cache group and index. {@code null} in case such statistics doesn't exists.
+     */
+    @MXBeanDescription("Number of physical leaf index's page reads for given cache group and index." +
+        "Can return null in case such statistics doesn't exists.")
+    @MXBeanParametersNames({"cacheGrpName", "idxName"})
+    @MXBeanParametersDescriptions({"Cache group name.", "Index name."})
+    public Long getIndexLeafPhysicalReads(String cacheGrpName, String idxName);
+
+    /**
+     * Gets number of logical inner index's page reads for given cache group and index.
+     *
+     * @param cacheGrpName Name of cache group.
+     * @param idxName Name of index.
+     * @return Number of logical inner index's page reads for given cache group and index. {@code null} in case such statistics doesn't exists.
+     */
+    @MXBeanDescription("Number of logical inner index's page reads for given cache group and index." +
+        "Can return null in case such statistics doesn't exists.")
+    @MXBeanParametersNames({"cacheGrpName", "idxName"})
+    @MXBeanParametersDescriptions({"Cache group name.", "Index name."})
+    public Long getIndexInnerLogicalReads(String cacheGrpName, String idxName);
+
+    /**
+     * Gets number of physical inner index's page reads for given cache group and index.
+     *
+     * @param cacheGrpName Name of cache group.
+     * @param idxName Name of index.
+     * @return Number of physical inner index's page reads for given cache group and index. {@code null} in case such
+     * statistics doesn't exists.
+     */
+    @MXBeanDescription("Number of physical inner index's page reads for given cache group and index." +
+        "Can return null in case such statistics doesn't exists.")
+    @MXBeanParametersNames({"cacheGrpName", "idxName"})
+    @MXBeanParametersDescriptions({"Cache group name.", "Index name."})
+    public Long getIndexInnerPhysicalReads(String cacheGrpName, String idxName);
+
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TransactionIntegrityWithPrimaryIndexCorruptionTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TransactionIntegrityWithPrimaryIndexCorruptionTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TransactionIntegrityWithPrimaryIndexCorruptionTest.java
index 486ae22..f16bd78 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TransactionIntegrityWithPrimaryIndexCorruptionTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/transactions/TransactionIntegrityWithPrimaryIndexCorruptionTest.java
@@ -33,6 +33,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.BPlusTree;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
 import org.apache.ignite.internal.processors.cache.tree.SearchRow;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -210,7 +211,7 @@ public class TransactionIntegrityWithPrimaryIndexCorruptionTest extends Abstract
 
                     return new PageHandler<BPlusTree.Get, BPlusTree.Result>() {
                         @Override public BPlusTree.Result run(int cacheId, long pageId, long page, long pageAddr, PageIO io,
-                            Boolean walPlc, BPlusTree.Get arg, int lvl) throws IgniteCheckedException {
+                            Boolean walPlc, BPlusTree.Get arg, int lvl, IoStatisticsHolder statHolder) throws IgniteCheckedException {
                             log.info("Invoked [cachedId=" + cacheId + ", hnd=" + arg.toString() +
                                 ", corruption=" + corruptionEnabled + ", row=" + arg.row() + ", rowCls=" + arg.row().getClass() + ']');
 
@@ -230,7 +231,7 @@ public class TransactionIntegrityWithPrimaryIndexCorruptionTest extends Abstract
                                 }
                             }
 
-                            return delegate.run(cacheId, pageId, page, pageAddr, io, walPlc, arg, lvl);
+                            return delegate.run(cacheId, pageId, page, pageAddr, io, walPlc, arg, lvl, statHolder);
                         }
 
                         @Override public boolean releaseAfterWrite(int cacheId, long pageId, long page, long pageAddr,

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListImplSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListImplSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListImplSelfTest.java
index 2f78c56..4c0d898 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListImplSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/database/CacheFreeListImplSelfTest.java
@@ -47,6 +47,7 @@ import org.apache.ignite.internal.processors.cache.persistence.freelist.CacheFre
 import org.apache.ignite.internal.processors.cache.persistence.freelist.FreeList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.CacheVersionIO;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 import org.apache.ignite.plugin.extensions.communication.MessageReader;
 import org.apache.ignite.plugin.extensions.communication.MessageWriter;
 import org.apache.ignite.testframework.GridTestUtils;
@@ -177,7 +178,7 @@ public class CacheFreeListImplSelfTest extends GridCommonAbstractTest {
 
             TestDataRow row = new TestDataRow(keySize, valSize);
 
-            list.insertDataRow(row);
+            list.insertDataRow(row, IoStatisticsHolderNoOp.INSTANCE);
 
             assertTrue(row.link() != 0L);
 
@@ -220,7 +221,7 @@ public class CacheFreeListImplSelfTest extends GridCommonAbstractTest {
 
                         TestDataRow row = new TestDataRow(keySize, valSize);
 
-                        list.insertDataRow(row);
+                        list.insertDataRow(row, IoStatisticsHolderNoOp.INSTANCE);
 
                         assertTrue(row.link() != 0L);
 
@@ -238,7 +239,7 @@ public class CacheFreeListImplSelfTest extends GridCommonAbstractTest {
                                 TestDataRow rmvd = stored.remove(row.link);
 
                                 if (rmvd != null) {
-                                    list.removeDataRowByLink(row.link);
+                                    list.removeDataRowByLink(row.link, IoStatisticsHolderNoOp.INSTANCE);
 
                                     break;
                                 }
@@ -268,7 +269,7 @@ public class CacheFreeListImplSelfTest extends GridCommonAbstractTest {
 
             TestDataRow row = new TestDataRow(keySize, valSize);
 
-            list.insertDataRow(row);
+            list.insertDataRow(row, IoStatisticsHolderNoOp.INSTANCE);
 
             assertTrue(row.link() != 0L);
 
@@ -303,7 +304,7 @@ public class CacheFreeListImplSelfTest extends GridCommonAbstractTest {
 
                 TestDataRow row = new TestDataRow(keySize, valSize);
 
-                list.insertDataRow(row);
+                list.insertDataRow(row, IoStatisticsHolderNoOp.INSTANCE);
 
                 assertTrue(row.link() != 0L);
 
@@ -321,7 +322,7 @@ public class CacheFreeListImplSelfTest extends GridCommonAbstractTest {
 
                     assertTrue(rmvd == row);
 
-                    list.removeDataRowByLink(row.link);
+                    list.removeDataRowByLink(row.link, IoStatisticsHolderNoOp.INSTANCE);
                 }
             }
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsCachePersistenceSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsCachePersistenceSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsCachePersistenceSelfTest.java
new file mode 100644
index 0000000..c203295
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsCachePersistenceSelfTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.stat;
+
+/**
+ * Tests for cache IO statistics for persistence mode.
+ */
+public class IoStatisticsCachePersistenceSelfTest extends IoStatisticsCacheSelfTest {
+
+    /** {@inheritDoc} */
+    @Override protected boolean persist() {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsCacheSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsCacheSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsCacheSelfTest.java
new file mode 100644
index 0000000..1eac96b
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsCacheSelfTest.java
@@ -0,0 +1,262 @@
+/*
+ * 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.stat;
+
+import com.google.common.collect.Sets;
+import java.util.Set;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.WALMode;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Assert;
+
+import static org.apache.ignite.internal.stat.IoStatisticsHolderIndex.HASH_PK_IDX_NAME;
+
+/**
+ * Tests for cache IO statistics for inmemory mode.
+ */
+public class IoStatisticsCacheSelfTest extends GridCommonAbstractTest {
+    /** */
+    protected static final String ATOMIC_CACHE_NAME = "ATOMIC_CACHE";
+
+    /** */
+    protected static final String MVCC_CACHE_NAME = "MVCC_CACHE";
+
+    /** */
+    protected static final String TRANSACTIONAL_CACHE_NAME = "TRANSACTIONAL_CACHE";
+
+    /** */
+    protected static final String CACHE1_IN_GROUP_NAME = "CACHE1_GROUP";
+
+    /** */
+    protected static final String CACHE2_IN_GROUP_NAME = "CACHE2_GROUP";
+
+    /** */
+    protected static final String CACHE_GROUP_NAME = "CACHE_GROUP_NAME";
+
+    /** */
+    protected static final Set<String> ALL_CACHE_GROUP_NAMES = Sets.newHashSet(
+        ATOMIC_CACHE_NAME, MVCC_CACHE_NAME, TRANSACTIONAL_CACHE_NAME, CACHE_GROUP_NAME);
+
+    /** */
+    protected static final int RECORD_COUNT = 100;
+
+    /** */
+    private static IgniteEx ignite;
+
+    /**
+     * @return {@code true} in case persistence mode enabled.
+     */
+    protected boolean persist() {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String name) throws Exception {
+        final IgniteConfiguration cfg = super.getConfiguration(name);
+
+        cfg.setConsistentId("consistentId");
+
+        final CacheConfiguration atomicCacheCfg = new CacheConfiguration()
+            .setAtomicityMode(CacheAtomicityMode.ATOMIC)
+            .setName(ATOMIC_CACHE_NAME);
+
+        final CacheConfiguration mvccCacheCfg = new CacheConfiguration()
+            .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL_SNAPSHOT)
+            .setName(MVCC_CACHE_NAME);
+
+        final CacheConfiguration transactionalCacheCfg = new CacheConfiguration()
+            .setAtomicityMode(CacheAtomicityMode.TRANSACTIONAL)
+            .setName(TRANSACTIONAL_CACHE_NAME);
+
+        final CacheConfiguration atomic1CacheGrpCfg = new CacheConfiguration()
+            .setAtomicityMode(CacheAtomicityMode.ATOMIC)
+            .setName(CACHE1_IN_GROUP_NAME)
+            .setGroupName(CACHE_GROUP_NAME);
+
+        final CacheConfiguration atomic2CacheGrpCfg = new CacheConfiguration()
+            .setAtomicityMode(CacheAtomicityMode.ATOMIC)
+            .setName(CACHE2_IN_GROUP_NAME)
+            .setGroupName(CACHE_GROUP_NAME);
+
+
+        DataStorageConfiguration dsCfg = new DataStorageConfiguration()
+            .setDefaultDataRegionConfiguration(
+                new DataRegionConfiguration()
+                    .setMaxSize(100L * 1024 * 1024)
+                    .setPersistenceEnabled(persist()))
+            .setPageSize(4 * 1024)
+            .setWalMode(WALMode.NONE);
+
+        cfg.setDataStorageConfiguration(dsCfg);
+
+        cfg.setCacheConfiguration(transactionalCacheCfg, atomicCacheCfg, mvccCacheCfg,
+            atomic1CacheGrpCfg, atomic2CacheGrpCfg);
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        super.beforeTestsStarted();
+
+        cleanPersistenceDir();
+
+        ignite = (IgniteEx)startGrid();
+
+        if (persist())
+            ignite.active(true);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        super.afterTestsStopped();
+
+        stopAllGrids();
+
+        cleanPersistenceDir();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        ignite.context().ioStats().reset();
+    }
+
+    /**
+     * Test statistics for TRANSACTIONAL cache.
+     */
+    public void testTransactonalCache() {
+        cacheTest(TRANSACTIONAL_CACHE_NAME, RECORD_COUNT, RECORD_COUNT * 3, RECORD_COUNT * 2);
+    }
+
+    /**
+     * Test statistics for MVCC cache.
+     */
+    public void testMvccCache() {
+        cacheTest(MVCC_CACHE_NAME, RECORD_COUNT, RECORD_COUNT * 6, RECORD_COUNT * 3);
+    }
+
+    /**
+     * Test statistics for ATOMIC cache.
+     */
+    public void testAtomicCache() {
+        cacheTest(ATOMIC_CACHE_NAME, RECORD_COUNT, RECORD_COUNT * 2, RECORD_COUNT);
+    }
+
+    /**
+     * Test statistics for three caches in the same time.
+     */
+    public void testForThreeCaches() {
+        prepareData(RECORD_COUNT, ATOMIC_CACHE_NAME, TRANSACTIONAL_CACHE_NAME, MVCC_CACHE_NAME);
+
+        IoStatisticsManager ioStatMgr = ignite.context().ioStats();
+
+        Set<String> statisticCacheNames = ioStatMgr.deriveStatisticNames(IoStatisticsType.CACHE_GROUP);
+
+        Assert.assertEquals(ALL_CACHE_GROUP_NAMES, statisticCacheNames);
+
+        Stream.of(ATOMIC_CACHE_NAME, TRANSACTIONAL_CACHE_NAME, MVCC_CACHE_NAME).forEach((cacheName) -> {
+            long logicalReads = ioStatMgr.logicalReads(IoStatisticsType.CACHE_GROUP, cacheName, null);
+
+            Assert.assertTrue(logicalReads > RECORD_COUNT);
+        });
+    }
+
+    /**
+     * Test statistics for two caches in the same cache group.
+     */
+    public void testCacheGroupCaches() {
+        prepareData(RECORD_COUNT, CACHE1_IN_GROUP_NAME, CACHE2_IN_GROUP_NAME);
+
+        IoStatisticsManager ioStatMgr = ignite.context().ioStats();
+
+        Set<String> statisticCacheNames = ioStatMgr.deriveStatisticNames(IoStatisticsType.CACHE_GROUP);
+
+        Assert.assertEquals(ALL_CACHE_GROUP_NAMES, statisticCacheNames);
+
+        long logicalReads = ioStatMgr.logicalReads(IoStatisticsType.CACHE_GROUP, CACHE_GROUP_NAME, null);
+
+        Assert.assertEquals(RECORD_COUNT * 6, logicalReads);
+    }
+
+    /**
+     * @param cacheName Name of cache.
+     * @param rowCnt Number of row need to put into cache.
+     * @param dataPageReads How many data page reads operation expected.
+     * @param idxPageReadsCnt How many index page reads scan expected.
+     */
+    protected void cacheTest(String cacheName, int rowCnt, int dataPageReads, int idxPageReadsCnt) {
+        prepareData(rowCnt, cacheName);
+
+        IoStatisticsManager ioStatMgr = ignite.context().ioStats();
+
+        Set<String> statisticCacheNames = ioStatMgr.deriveStatisticNames(IoStatisticsType.CACHE_GROUP);
+
+        Assert.assertEquals(ALL_CACHE_GROUP_NAMES, statisticCacheNames);
+
+        Assert.assertTrue(statisticCacheNames.contains(cacheName));
+
+        long logicalReadsCache = ioStatMgr.logicalReads(IoStatisticsType.CACHE_GROUP, cacheName, null);
+
+        Assert.assertEquals(dataPageReads, logicalReadsCache);
+
+        long logicalReadsIdx = ioStatMgr.logicalReads(IoStatisticsType.HASH_INDEX, cacheName, HASH_PK_IDX_NAME);
+
+        Assert.assertEquals(idxPageReadsCnt, logicalReadsIdx);
+
+    }
+
+    /**
+     * Warm up and fill cache.
+     *
+     * @param cacheNames Names of caches to populate.
+     * @param cnt Number of entries to put.
+     */
+    private void prepareData(int cnt, String... cacheNames) {
+        //Need to initialize partition and data memory pages
+        for (String cacheName : cacheNames) {
+
+            IgniteCache cache = ignite.cache(cacheName);
+
+            for (int i = 0; i < cnt; i++) {
+                cache.put(i, i);
+                cache.put(i, i); //Second invocation required to warm up MVCC cache to fill old versions chains.
+            }
+        }
+
+        ignite.context().ioStats().reset();
+
+        for (String cacheName : cacheNames) {
+
+            IgniteCache cache = ignite.cache(cacheName);
+
+            for (int i = 0; i < cnt; i++)
+                cache.put(i, i);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsManagerSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsManagerSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsManagerSelfTest.java
new file mode 100644
index 0000000..3b03c46
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsManagerSelfTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.stat;
+
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.configuration.WALMode;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Assert;
+
+import static org.apache.ignite.internal.stat.IoStatisticsHolderIndex.HASH_PK_IDX_NAME;
+
+/**
+ * Tests for IO statistic manager.
+ */
+public class IoStatisticsManagerSelfTest extends GridCommonAbstractTest {
+
+    /** */
+    private static final int RECORD_COUNT = 5000;
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        stopAllGrids();
+
+        cleanPersistenceDir();
+    }
+
+    /**
+     * Test LOCAL_NODE statistics tracking for persistent cache.
+     *
+     * @throws Exception In case of failure.
+     */
+    public void testNotPersistentIOGlobalStat() throws Exception {
+        ioStatGlobalPageTrackTest(false);
+    }
+
+    /**
+     * Test LOCAL_NODE statistics tracking for not persistent cache.
+     *
+     * @throws Exception In case of failure.
+     */
+    public void testPersistentIOGlobalStat() throws Exception {
+        ioStatGlobalPageTrackTest(true);
+    }
+
+    /**
+     * Check LOCAL_NODE statistics tracking.
+     *
+     * @param isPersistent {@code true} in case persistence should be enable.
+     * @throws Exception In case of failure.
+     */
+    private void ioStatGlobalPageTrackTest(boolean isPersistent) throws Exception {
+        IoStatisticsManager ioStatMgr = prepareData(isPersistent);
+
+        long physicalReadsCnt = ioStatMgr.physicalReads(IoStatisticsType.CACHE_GROUP, DEFAULT_CACHE_NAME, null);
+
+        if (isPersistent)
+            Assert.assertTrue(physicalReadsCnt>0);
+        else
+            Assert.assertEquals(0, physicalReadsCnt);
+
+        Long logicalReads = ioStatMgr.logicalReads(IoStatisticsType.HASH_INDEX, DEFAULT_CACHE_NAME, HASH_PK_IDX_NAME);
+
+        Assert.assertNotNull(logicalReads);
+
+        Assert.assertEquals(RECORD_COUNT, logicalReads.longValue());
+    }
+
+    /**
+     * Prepare Ignite instance and fill cache.
+     *
+     * @param isPersistent {@code true} in case persistence should be enable.
+     * @return IO statistic manager.
+     * @throws Exception In case of failure.
+     */
+    @NotNull private IoStatisticsManager prepareData(boolean isPersistent) throws Exception {
+        IgniteEx ign = prepareIgnite(isPersistent);
+
+        IoStatisticsManager ioStatMgr = ign.context().ioStats();
+
+        IgniteCache cache = ign.getOrCreateCache(DEFAULT_CACHE_NAME);
+
+        ioStatMgr.reset();
+
+        for (int i = 0; i < RECORD_COUNT; i++)
+            cache.put("KEY-" + i, "VAL-" + i);
+
+        return ioStatMgr;
+    }
+
+    /**
+     * Create Ignite configuration.
+     *
+     * @param isPersist {@code true} in case persistence should be enable.
+     * @return Ignite configuration.
+     * @throws Exception In case of failure.
+     */
+    private IgniteConfiguration getConfiguration(boolean isPersist) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration();
+
+        if (isPersist) {
+            DataStorageConfiguration dsCfg = new DataStorageConfiguration()
+                .setDefaultDataRegionConfiguration(
+                    new DataRegionConfiguration()
+                        .setMaxSize(10L * 1024 * 1024)
+                        .setPersistenceEnabled(true))
+                .setWalMode(WALMode.LOG_ONLY);
+
+            cfg.setDataStorageConfiguration(dsCfg);
+        }
+
+        return cfg;
+    }
+
+    /**
+     * Start Ignite grid and create cache.
+     *
+     * @param isPersist {@code true} in case Ignate should use persistente storage.
+     * @return Started Ignite instance.
+     * @throws Exception In case of failure.
+     */
+    private IgniteEx prepareIgnite(boolean isPersist) throws Exception {
+        IgniteEx ignite = startGrid(getConfiguration(isPersist));
+
+        ignite.cluster().active(true);
+
+        ignite.createCache(new CacheConfiguration<String, String>(DEFAULT_CACHE_NAME));
+
+        return ignite;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsMetricsLocalMXBeanImplSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsMetricsLocalMXBeanImplSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsMetricsLocalMXBeanImplSelfTest.java
new file mode 100644
index 0000000..0fa6baa
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/stat/IoStatisticsMetricsLocalMXBeanImplSelfTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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.stat;
+
+import java.lang.management.ManagementFactory;
+import java.time.format.DateTimeFormatter;
+import javax.management.MBeanServer;
+import javax.management.MBeanServerInvocationHandler;
+import javax.management.ObjectName;
+import junit.framework.Assert;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.mxbean.IoStatisticsMetricsMXBean;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+
+import static org.apache.ignite.internal.stat.IoStatisticsHolderIndex.HASH_PK_IDX_NAME;
+
+/**
+ * Test of local node IO statistics MX bean.
+ */
+public class IoStatisticsMetricsLocalMXBeanImplSelfTest extends GridCommonAbstractTest {
+    /** */
+    private static IgniteEx ignite;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String name) throws Exception {
+        final IgniteConfiguration cfg = super.getConfiguration(name);
+
+        final CacheConfiguration cCfg = new CacheConfiguration()
+            .setName(DEFAULT_CACHE_NAME);
+
+        cfg.setCacheConfiguration(cCfg);
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        super.beforeTestsStarted();
+
+        ignite = startGrid(0);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        super.afterTestsStopped();
+
+        stopAllGrids();
+    }
+
+    /**
+     * Simple test JMX bean for indexes IO stats.
+     *
+     * @throws Exception In case of failure.
+     */
+    public void testIndexBasic() throws Exception {
+        IoStatisticsMetricsMXBean bean = ioStatMXBean();
+
+        IoStatisticsManager ioStatMgr = ignite.context().ioStats();
+
+        Assert.assertEquals(ioStatMgr.startTime().toEpochSecond(), bean.getStartTime());
+
+        Assert.assertEquals(ioStatMgr.startTime().format(DateTimeFormatter.ISO_DATE_TIME), bean.getStartTimeLocal());
+
+        bean.reset();
+
+        Assert.assertEquals(ioStatMgr.startTime().toEpochSecond(), bean.getStartTime());
+
+        Assert.assertEquals(ioStatMgr.startTime().format(DateTimeFormatter.ISO_DATE_TIME), bean.getStartTimeLocal());
+
+        int cnt = 100;
+
+        populateCache(cnt);
+
+        long idxLeafLogicalCnt = bean.getIndexLeafLogicalReads(DEFAULT_CACHE_NAME, HASH_PK_IDX_NAME);
+
+        Assert.assertEquals(cnt, idxLeafLogicalCnt);
+
+        long idxLeafPhysicalCnt = bean.getIndexLeafPhysicalReads(DEFAULT_CACHE_NAME, HASH_PK_IDX_NAME);
+
+        Assert.assertEquals(0, idxLeafPhysicalCnt);
+
+        long idxInnerLogicalCnt = bean.getIndexInnerLogicalReads(DEFAULT_CACHE_NAME, HASH_PK_IDX_NAME);
+
+        Assert.assertEquals(0, idxInnerLogicalCnt);
+
+        long idxInnerPhysicalCnt = bean.getIndexInnerPhysicalReads(DEFAULT_CACHE_NAME, HASH_PK_IDX_NAME);
+
+        Assert.assertEquals(0, idxInnerPhysicalCnt);
+
+        Long aggregatedIdxLogicalReads = bean.getIndexLogicalReads(DEFAULT_CACHE_NAME, HASH_PK_IDX_NAME);
+
+        Assert.assertNotNull(aggregatedIdxLogicalReads);
+
+        Assert.assertEquals(aggregatedIdxLogicalReads.longValue(), idxLeafLogicalCnt + idxLeafPhysicalCnt +
+            idxInnerLogicalCnt + idxInnerPhysicalCnt);
+
+        Long aggregatedIdxPhysicalReads = bean.getIndexPhysicalReads(DEFAULT_CACHE_NAME, HASH_PK_IDX_NAME);
+
+        Assert.assertNotNull(aggregatedIdxPhysicalReads);
+
+        Assert.assertEquals(0, aggregatedIdxPhysicalReads.longValue());
+
+        String formatted = bean.getIndexStatistics(DEFAULT_CACHE_NAME, HASH_PK_IDX_NAME);
+
+        Assert.assertEquals("HASH_INDEX default.HASH_PK [LOGICAL_READS_LEAF=100, LOGICAL_READS_INNER=0, " +
+            "PHYSICAL_READS_INNER=0, PHYSICAL_READS_LEAF=0]", formatted);
+
+        String unexistedStats = bean.getIndexStatistics("unknownCache", "unknownIdx");
+
+        Assert.assertEquals("SORTED_INDEX unknownCache.unknownIdx []", unexistedStats);
+    }
+
+    /**
+     * Simple test JMX bean for caches IO stats.
+     *
+     * @throws Exception In case of failure.
+     */
+    public void testCacheBasic() throws Exception {
+        IoStatisticsMetricsMXBean bean = ioStatMXBean();
+
+        IoStatisticsManager ioStatMgr = ignite.context().ioStats();
+
+        Assert.assertEquals(ioStatMgr.startTime().toEpochSecond(), bean.getStartTime());
+
+        Assert.assertEquals(ioStatMgr.startTime().format(DateTimeFormatter.ISO_DATE_TIME), bean.getStartTimeLocal());
+
+        bean.reset();
+
+        Assert.assertEquals(ioStatMgr.startTime().toEpochSecond(), bean.getStartTime());
+
+        Assert.assertEquals(ioStatMgr.startTime().format(DateTimeFormatter.ISO_DATE_TIME), bean.getStartTimeLocal());
+
+        int cnt = 100;
+
+        warmUpMemmory(bean, cnt);
+
+        populateCache(cnt);
+
+        Long cacheLogicalReadsCnt = bean.getCacheGroupLogicalReads(DEFAULT_CACHE_NAME);
+
+        Assert.assertNotNull(cacheLogicalReadsCnt);
+
+        Assert.assertEquals(cnt, cacheLogicalReadsCnt.longValue());
+
+        Long cachePhysicalReadsCnt = bean.getCacheGroupPhysicalReads(DEFAULT_CACHE_NAME);
+
+        Assert.assertNotNull(cachePhysicalReadsCnt);
+
+        Assert.assertEquals(0, cachePhysicalReadsCnt.longValue());
+
+        String formatted = bean.getCacheGroupStatistics(DEFAULT_CACHE_NAME);
+
+        Assert.assertEquals("CACHE_GROUP default [LOGICAL_READS=100, PHYSICAL_READS=0]", formatted);
+
+        String unexistedStats = bean.getCacheGroupStatistics("unknownCache");
+
+        Assert.assertEquals("CACHE_GROUP unknownCache []", unexistedStats);
+    }
+
+    /**
+     * Warm up memmory to allocate partitions cache pages related to inserting keys.
+     *
+     * @param bean JMX bean.
+     * @param cnt Number of inserting elements.
+     */
+    private void warmUpMemmory(IoStatisticsMetricsMXBean bean, int cnt) {
+        populateCache(cnt);
+
+        clearCache(cnt);
+
+        bean.reset();
+    }
+
+    /**
+     * @param cnt Number of inserting elements.
+     */
+    private void populateCache(int cnt) {
+        for (int i = 0; i < cnt; i++)
+            ignite.cache(DEFAULT_CACHE_NAME).put(i, i);
+    }
+
+    /**
+     * @param cnt Number of removing elements.
+     */
+    private void clearCache(int cnt) {
+        for (int i = 0; i < cnt; i++)
+            ignite.cache(DEFAULT_CACHE_NAME).remove(i);
+    }
+
+    /**
+     * @return IO statistics MX bean for node with given index.
+     * @throws Exception In case of failure.
+     */
+    private IoStatisticsMetricsMXBean ioStatMXBean() throws Exception {
+        ObjectName mbeanName = U.makeMBeanName(getTestIgniteInstanceName(0), "IOMetrics",
+            IoStatisticsMetricsLocalMXBeanImpl.class.getSimpleName());
+
+        MBeanServer mbeanSrv = ManagementFactory.getPlatformMBeanServer();
+
+        if (!mbeanSrv.isRegistered(mbeanName))
+            fail("MBean is not registered: " + mbeanName.getCanonicalName());
+
+        return MBeanServerInvocationHandler.newProxyInstance(mbeanSrv, mbeanName, IoStatisticsMetricsMXBean.class, false);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java
index f2a69bb..4df301a 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2Tree.java
@@ -41,6 +41,7 @@ import org.apache.ignite.internal.processors.query.h2.database.io.H2RowLinkIO;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2KeyValueRowOnheap;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2Row;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2SearchRow;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.h2.result.SearchRow;
@@ -87,6 +88,9 @@ public abstract class H2Tree extends BPlusTree<GridH2SearchRow, GridH2Row> {
     private final String idxName;
 
     /** */
+    private final IoStatisticsHolder stats;
+
+    /** */
     private final Comparator<Value> comp = new Comparator<Value>() {
         @Override public int compare(Value o1, Value o2) {
             return compareValues(o1, o2);
@@ -134,6 +138,7 @@ public abstract class H2Tree extends BPlusTree<GridH2SearchRow, GridH2Row> {
      * @param mvccEnabled Mvcc flag.
      * @param failureProcessor if the tree is corrupted.
      * @param log Logger.
+     * @param stats Statistics holder.
      * @throws IgniteCheckedException If failed.
      */
     protected H2Tree(
@@ -157,10 +162,13 @@ public abstract class H2Tree extends BPlusTree<GridH2SearchRow, GridH2Row> {
         boolean mvccEnabled,
         @Nullable H2RowCache rowCache,
         @Nullable FailureProcessor failureProcessor,
-        IgniteLogger log
+        IgniteLogger log,
+        IoStatisticsHolder stats
     ) throws IgniteCheckedException {
         super(name, grpId, pageMem, wal, globalRmvId, metaPageId, reuseList, failureProcessor);
 
+        this.stats = stats;
+
         if (!initNew) {
             // Page is ready - read meta information.
             MetaPageInfo metaInfo = getMetaInfo();
@@ -515,6 +523,11 @@ public abstract class H2Tree extends BPlusTree<GridH2SearchRow, GridH2Row> {
         }
     }
 
+    /** {@inheritDoc} */
+    @Override protected IoStatisticsHolder statisticsHolder() {
+        return stats;
+    }
+
     /**
      * @return {@code true} In case use unwrapped columns for PK
      */

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2TreeIndex.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2TreeIndex.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2TreeIndex.java
index 65be8d6..40d0a9f 100644
--- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2TreeIndex.java
+++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/database/H2TreeIndex.java
@@ -38,6 +38,8 @@ import org.apache.ignite.internal.processors.query.h2.opt.GridH2QueryContext;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2Row;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2SearchRow;
 import org.apache.ignite.internal.processors.query.h2.opt.GridH2Table;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
+import org.apache.ignite.internal.stat.IoStatisticsType;
 import org.apache.ignite.internal.util.IgniteTree;
 import org.apache.ignite.internal.util.lang.GridCursor;
 import org.apache.ignite.internal.util.typedef.F;
@@ -71,7 +73,7 @@ public class H2TreeIndex extends H2TreeIndexBase {
     /** Cache context. */
     private final GridCacheContext<?, ?> cctx;
 
-    /** Table name/ */
+    /** Table name. */
     private final String tblName;
 
     /** */
@@ -148,6 +150,12 @@ public class H2TreeIndex extends H2TreeIndexBase {
 
         AtomicInteger maxCalculatedInlineSize = new AtomicInteger();
 
+        IoStatisticsHolder stats = cctx.kernalContext().ioStats().register(
+            IoStatisticsType.SORTED_INDEX,
+            cctx.name(),
+            idxName
+        );
+
         for (int i = 0; i < segments.length; i++) {
             db.checkpointReadLock();
 
@@ -175,7 +183,8 @@ public class H2TreeIndex extends H2TreeIndexBase {
                     cctx.mvccEnabled(),
                     rowCache,
                     cctx.kernalContext().failure(),
-                    log) {
+                    log,
+                    stats) {
                     @Override public int compareValues(Value v1, Value v2) {
                         return v1 == v2 ? 0 : table.compareTypeSafe(v1, v2);
                     }
@@ -270,10 +279,10 @@ public class H2TreeIndex extends H2TreeIndexBase {
 
     /** {@inheritDoc} */
     @Override public Cursor find(Session ses, SearchRow lower, SearchRow upper) {
-        try {
-            assert lower == null || lower instanceof GridH2SearchRow : lower;
-            assert upper == null || upper instanceof GridH2SearchRow : upper;
+        assert lower == null || lower instanceof GridH2SearchRow : lower;
+        assert upper == null || upper instanceof GridH2SearchRow : upper;
 
+        try {
             int seg = threadLocalSegment();
 
             H2Tree tree = treeForRead(seg);
@@ -361,9 +370,9 @@ public class H2TreeIndex extends H2TreeIndexBase {
 
     /** {@inheritDoc} */
     @Override public boolean removex(SearchRow row) {
-        try {
-            assert row instanceof GridH2SearchRow : row;
+        assert row instanceof GridH2SearchRow : row;
 
+        try {
             InlineIndexHelper.setCurrentInlineIndexes(inlineIdxs);
 
             int seg = segmentForRow(row);
@@ -412,9 +421,9 @@ public class H2TreeIndex extends H2TreeIndexBase {
     }
 
     /** {@inheritDoc} */
-    @Override public void destroy(boolean rmvIndex) {
+    @Override public void destroy(boolean rmvIdx) {
         try {
-            if (cctx.affinityNode() && rmvIndex) {
+            if (cctx.affinityNode() && rmvIdx) {
                 assert cctx.shared().database().checkpointLockIsHeldByThread();
 
                 for (int i = 0; i < segments.length; i++) {
@@ -430,7 +439,7 @@ public class H2TreeIndex extends H2TreeIndexBase {
             throw new IgniteException(e);
         }
         finally {
-            super.destroy(rmvIndex);
+            super.destroy(rmvIdx);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/IoStatisticsBasicIndexSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/IoStatisticsBasicIndexSelfTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/IoStatisticsBasicIndexSelfTest.java
new file mode 100644
index 0000000..c7b0fa5
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/IoStatisticsBasicIndexSelfTest.java
@@ -0,0 +1,500 @@
+/*
+ * 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 com.google.common.collect.Sets;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.QueryIndex;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.DataRegionConfiguration;
+import org.apache.ignite.configuration.DataStorageConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.stat.IoStatisticsManager;
+import org.apache.ignite.internal.stat.IoStatisticsType;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder;
+import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.junit.Assert;
+
+/**
+ * A set of basic tests for caches with indexes.
+ */
+public class IoStatisticsBasicIndexSelfTest extends GridCommonAbstractTest {
+    /** */
+    private static final TcpDiscoveryIpFinder IP_FINDER = new TcpDiscoveryVmIpFinder(true);
+
+    /** */
+    private static final int NUMBER_OF_PK_SORTED_INDEXES = 1;
+
+    /** */
+    private static final Set<String> PK_HASH_INDEXES = Sets.newHashSet(DEFAULT_CACHE_NAME);
+
+    /** */
+    private Collection<QueryIndex> indexes;
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        assertNotNull(indexes);
+
+        IgniteConfiguration igniteCfg = super.getConfiguration(igniteInstanceName);
+
+        igniteCfg.setDiscoverySpi(
+            new TcpDiscoverySpi().setIpFinder(IP_FINDER)
+        );
+
+        LinkedHashMap<String, String> fields = new LinkedHashMap<>();
+        fields.put("keyStr", String.class.getName());
+        fields.put("keyLong", Long.class.getName());
+        fields.put("keyPojo", Pojo.class.getName());
+        fields.put("valStr", String.class.getName());
+        fields.put("valLong", Long.class.getName());
+        fields.put("valPojo", Pojo.class.getName());
+
+        CacheConfiguration<Key, Val> ccfg = new CacheConfiguration<Key, Val>(DEFAULT_CACHE_NAME)
+            .setQueryEntities(Collections.singleton(
+                new QueryEntity()
+                    .setKeyType(Key.class.getName())
+                    .setValueType(Val.class.getName())
+                    .setFields(fields)
+                    .setIndexes(indexes)
+            ));
+
+        igniteCfg.setCacheConfiguration(ccfg);
+
+        igniteCfg.setDataStorageConfiguration(new DataStorageConfiguration()
+            .setDefaultDataRegionConfiguration(
+                new DataRegionConfiguration().setPersistenceEnabled(true)
+            )
+        );
+
+        return igniteCfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        super.beforeTest();
+
+        stopAllGrids();
+
+        cleanPersistenceDir();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        stopAllGrids();
+
+        cleanPersistenceDir();
+
+        indexes = null;
+
+        super.afterTest();
+    }
+
+    /**
+     * @throws Exception In case of failure.
+     */
+    public void testNoIndexes() throws Exception {
+        indexes = Collections.emptyList();
+
+        startGrid();
+
+        grid().cluster().active(true);
+
+        populateCache();
+
+        checkStat();
+
+        checkAll();
+
+        checkStat();
+    }
+
+    /**
+     * @throws Exception In case of failure.
+     */
+    public void testAllIndexes() throws Exception {
+        indexes = Arrays.asList(
+            new QueryIndex("keyStr"),
+            new QueryIndex("keyLong"),
+            new QueryIndex("keyPojo"),
+            new QueryIndex("valStr"),
+            new QueryIndex("valLong"),
+            new QueryIndex("valPojo")
+        );
+
+        startGrid();
+
+        grid().cluster().active(true);
+
+        populateCache();
+
+        checkStat();
+
+        checkAll();
+
+        checkStat();
+    }
+
+    /** */
+    private void checkStat() {
+        IoStatisticsManager ioStat = grid().context().ioStats();
+
+        Set<String> hashIndexes = ioStat.deriveStatisticNames(IoStatisticsType.HASH_INDEX);
+
+        Assert.assertEquals(PK_HASH_INDEXES, hashIndexes);
+
+        Set<String> sortedIndexCaches = ioStat.deriveStatisticNames(IoStatisticsType.SORTED_INDEX);
+
+        Assert.assertEquals(1, sortedIndexCaches.size());
+
+        Set<String> sortedIdxNames = ioStat.deriveStatisticSubNames(IoStatisticsType.SORTED_INDEX,
+            sortedIndexCaches.toArray()[0].toString());
+
+        Assert.assertEquals(sortedIndexCaches.toString(), indexes.size() + NUMBER_OF_PK_SORTED_INDEXES, sortedIdxNames.size());
+
+        for (String idxName : sortedIdxNames) {
+            Long logicalReads = ioStat.logicalReads(IoStatisticsType.SORTED_INDEX, DEFAULT_CACHE_NAME, idxName);
+
+            Assert.assertNotNull(idxName, logicalReads);
+
+            Assert.assertTrue(logicalReads > 0);
+        }
+
+        ioStat.reset();
+    }
+
+    /** */
+    private void checkAll() {
+        IgniteCache<Key, Val> cache = grid().cache(DEFAULT_CACHE_NAME);
+
+        checkRemovePut(cache);
+
+        checkSelectAll(cache);
+
+        checkSelectStringEqual(cache);
+
+        checkSelectLongEqual(cache);
+
+        checkSelectStringRange(cache);
+
+        checkSelectLongRange(cache);
+    }
+
+    /** */
+    private void populateCache() {
+        IgniteCache<Key, Val> cache = grid().cache(DEFAULT_CACHE_NAME);
+
+        for (int i = 0; i < 100; i++)
+            cache.put(key(i), val(i));
+    }
+
+    /**
+     * @param cache Cache.
+     */
+    private void checkRemovePut(IgniteCache<Key, Val> cache) {
+        final int INT = 24;
+
+        assertEquals(val(INT), cache.get(key(INT)));
+
+        cache.remove(key(INT));
+
+        assertNull(cache.get(key(INT)));
+
+        cache.put(key(INT), val(INT));
+
+        assertEquals(val(INT), cache.get(key(INT)));
+    }
+
+    /**
+     * @param cache Cache.
+     */
+    private void checkSelectAll(IgniteCache<Key, Val> cache) {
+        List<List<?>> data = cache.query(new SqlFieldsQuery("select _key, _val from Val")).getAll();
+
+        assertEquals(100, data.size());
+
+        for (List<?> row : data) {
+            Key key = (Key)row.get(0);
+
+            Val val = (Val)row.get(1);
+
+            long i = key.keyLong;
+
+            assertEquals(key(i), key);
+
+            assertEquals(val(i), val);
+        }
+    }
+
+    /**
+     * @param cache Cache.
+     */
+    private void checkSelectStringEqual(IgniteCache<Key, Val> cache) {
+        final String STR = "foo011";
+
+        final long LONG = 11;
+
+        List<List<?>> data = cache.query(new SqlFieldsQuery("select _key, _val from Val where keyStr = ?")
+            .setArgs(STR))
+            .getAll();
+
+        assertEquals(1, data.size());
+
+        List<?> row = data.get(0);
+
+        assertEquals(key(LONG), row.get(0));
+
+        assertEquals(val(LONG), row.get(1));
+    }
+
+    /**
+     * @param cache Cache.
+     */
+    private void checkSelectLongEqual(IgniteCache<Key, Val> cache) {
+        final long LONG = 42;
+
+        List<List<?>> data = cache.query(new SqlFieldsQuery("select _key, _val from Val where valLong = ?")
+            .setArgs(LONG))
+            .getAll();
+
+        assertEquals(1, data.size());
+
+        List<?> row = data.get(0);
+
+        assertEquals(key(LONG), row.get(0));
+
+        assertEquals(val(LONG), row.get(1));
+    }
+
+    /**
+     * @param cache Cache.
+     */
+    private void checkSelectStringRange(IgniteCache<Key, Val> cache) {
+        final String PREFIX = "foo06";
+
+        List<List<?>> data = cache.query(new SqlFieldsQuery("select _key, _val from Val where keyStr like ?")
+            .setArgs(PREFIX + "%"))
+            .getAll();
+
+        assertEquals(10, data.size());
+
+        for (List<?> row : data) {
+            Key key = (Key)row.get(0);
+
+            Val val = (Val)row.get(1);
+
+            long i = key.keyLong;
+
+            assertEquals(key(i), key);
+
+            assertEquals(val(i), val);
+
+            assertTrue(key.keyStr.startsWith(PREFIX));
+        }
+    }
+
+    /**
+     * @param cache Cache.
+     */
+    private void checkSelectLongRange(IgniteCache<Key, Val> cache) {
+        final long RANGE_START = 70;
+
+        final long RANGE_END = 80;
+
+        List<List<?>> data = cache.query(
+            new SqlFieldsQuery("select _key, _val from Val where valLong >= ? and valLong < ?")
+                .setArgs(RANGE_START, RANGE_END))
+            .getAll();
+
+        assertEquals(10, data.size());
+
+        for (List<?> row : data) {
+            Key key = (Key)row.get(0);
+
+            Val val = (Val)row.get(1);
+
+            long i = key.keyLong;
+
+            assertEquals(key(i), key);
+
+            assertEquals(val(i), val);
+
+            assertTrue(i >= RANGE_START && i < RANGE_END);
+        }
+    }
+
+    /**
+     * @param i Index to generate key.
+     * @return generated key.
+     */
+    private static Key key(long i) {
+        return new Key(String.format("foo%03d", i), i, new Pojo(i));
+    }
+
+    /**
+     * @param i Index to generate key.
+     * @return generated key.
+     */
+    private static Val val(long i) {
+        return new Val(String.format("bar%03d", i), i, new Pojo(i));
+    }
+
+    /** */
+    private static class Key {
+        /** */
+        private String keyStr;
+
+        /** */
+        private long keyLong;
+
+        /** */
+        private Pojo keyPojo;
+
+        /**
+         * @param str String.
+         * @param aLong Long.
+         * @param pojo Pojo.
+         */
+        private Key(String str, long aLong, Pojo pojo) {
+            keyStr = str;
+            keyLong = aLong;
+            keyPojo = pojo;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            Key key = (Key)o;
+
+            return keyLong == key.keyLong &&
+                Objects.equals(keyStr, key.keyStr) &&
+                Objects.equals(keyPojo, key.keyPojo);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return Objects.hash(keyStr, keyLong, keyPojo);
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(Key.class, this);
+        }
+    }
+
+    /** */
+    private static class Val {
+        /** */
+        private String valStr;
+
+        /** */
+        private long valLong;
+
+        /** */
+        private Pojo valPojo;
+
+        /**
+         * @param str String.
+         * @param aLong Long.
+         * @param pojo Pojo.
+         */
+        private Val(String str, long aLong, Pojo pojo) {
+            valStr = str;
+            valLong = aLong;
+            valPojo = pojo;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            Val val = (Val)o;
+
+            return valLong == val.valLong &&
+                Objects.equals(valStr, val.valStr) &&
+                Objects.equals(valPojo, val.valPojo);
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return Objects.hash(valStr, valLong, valPojo);
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(Val.class, this);
+        }
+    }
+
+    /** */
+    private static class Pojo {
+        /** */
+        private long pojoLong;
+
+        /**
+         * @param pojoLong Long.
+         */
+        private Pojo(long pojoLong) {
+            this.pojoLong = pojoLong;
+        }
+
+        /** {@inheritDoc} */
+        @Override public boolean equals(Object o) {
+            if (this == o)
+                return true;
+
+            if (o == null || getClass() != o.getClass())
+                return false;
+
+            Pojo pojo = (Pojo)o;
+
+            return pojoLong == pojo.pojoLong;
+        }
+
+        /** {@inheritDoc} */
+        @Override public int hashCode() {
+            return Objects.hash(pojoLong);
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(Pojo.class, this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalRecoveryTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalRecoveryTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalRecoveryTest.java
index 60e0ca0..b29a679 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalRecoveryTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/persistence/db/wal/IgniteWalRecoveryTest.java
@@ -88,6 +88,7 @@ import org.apache.ignite.internal.processors.cache.persistence.metastorage.MetaS
 import org.apache.ignite.internal.processors.cache.persistence.pagemem.PageMemoryEx;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.TrackingPageIO;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 import org.apache.ignite.internal.util.GridUnsafe;
 import org.apache.ignite.internal.util.lang.IgniteInClosureX;
 import org.apache.ignite.internal.util.typedef.F;
@@ -1386,8 +1387,9 @@ public class IgniteWalRecoveryTest extends GridCommonAbstractTest {
 
             ignite0.context().cache().context().database().checkpointReadLock();
 
-            try {
-                long page = pageMem.acquirePage(fullId.groupId(), fullId.pageId(), true);
+                try {
+                    long page = pageMem.acquirePage(
+                        fullId.groupId(), fullId.pageId(), IoStatisticsHolderNoOp.INSTANCE, true);
 
                 try {
                     long bufPtr = pageMem.writeLock(fullId.groupId(), fullId.pageId(), page, true);

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/indexing/src/test/java/org/apache/ignite/util/GridCommandHandlerIndexingTest.java
----------------------------------------------------------------------
diff --git a/modules/indexing/src/test/java/org/apache/ignite/util/GridCommandHandlerIndexingTest.java b/modules/indexing/src/test/java/org/apache/ignite/util/GridCommandHandlerIndexingTest.java
index dc5011f..3e47372 100644
--- a/modules/indexing/src/test/java/org/apache/ignite/util/GridCommandHandlerIndexingTest.java
+++ b/modules/indexing/src/test/java/org/apache/ignite/util/GridCommandHandlerIndexingTest.java
@@ -45,6 +45,7 @@ import org.apache.ignite.internal.processors.cache.persistence.GridCacheDatabase
 import org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
 import org.apache.ignite.internal.processors.cache.tree.SearchRow;
 import org.apache.ignite.internal.processors.query.GridQueryProcessor;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 import org.apache.ignite.internal.util.lang.GridIterator;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.CU;
@@ -243,7 +244,7 @@ public class GridCommandHandlerIndexingTest extends GridCommandHandlerTest {
                             new SearchRow(cacheId, ctx.toCacheKeyObject(entry.getKey())));
 
                         if (oldRow != null)
-                            U.invoke(rowStore.getClass(), rowStore, "removeRow", oldRow.link());
+                            U.invoke(rowStore.getClass(), rowStore, "removeRow", oldRow.link(), IoStatisticsHolderNoOp.INSTANCE);
                     }
                     catch (IgniteCheckedException e) {
                         System.out.println("Failed to remove key skipping indexes: " + entry);


[2/3] ignite git commit: IGNITE-9777: Implemented IO statistics for logic and physical page reads. This closes #4934.

Posted by vo...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
index e79570e..0358975 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/PagesList.java
@@ -44,6 +44,8 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersion
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseBag;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 import org.apache.ignite.internal.util.GridArrays;
 import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.typedef.F;
@@ -102,7 +104,8 @@ public abstract class PagesList extends DataStructure {
             PageIO iox,
             Boolean walPlc,
             Void ignore,
-            int bucket) throws IgniteCheckedException {
+            int bucket,
+            IoStatisticsHolder statHolder) throws IgniteCheckedException {
             assert getPageId(pageAddr) == pageId;
 
             PagesListNodeIO io = (PagesListNodeIO)iox;
@@ -166,7 +169,7 @@ public abstract class PagesList extends DataStructure {
 
                 while (nextId != 0) {
                     final long pageId = nextId;
-                    final long page = acquirePage(pageId);
+                    final long page = acquirePage(pageId, IoStatisticsHolderNoOp.INSTANCE);
 
                     try {
                         long pageAddr = readLock(pageId, page); // No concurrent recycling on init.
@@ -211,7 +214,7 @@ public abstract class PagesList extends DataStructure {
 
                         while (prevId != 0L) {
                             final long pageId = prevId;
-                            final long page = acquirePage(pageId);
+                            final long page = acquirePage(pageId, IoStatisticsHolderNoOp.INSTANCE);
                             try  {
                                 long pageAddr = readLock(pageId, page);
 
@@ -291,7 +294,7 @@ public abstract class PagesList extends DataStructure {
                                 }
 
                                 curId = nextPageId;
-                                curPage = acquirePage(curId);
+                                curPage = acquirePage(curId, IoStatisticsHolderNoOp.INSTANCE);
                                 curAddr = writeLock(curId, curPage);
 
                                 curIo = PagesListMetaIO.VERSIONS.latest();
@@ -302,7 +305,7 @@ public abstract class PagesList extends DataStructure {
                                 releaseAndClose(curId, curPage, curAddr);
 
                                 curId = nextPageId;
-                                curPage = acquirePage(curId);
+                                curPage = acquirePage(curId, IoStatisticsHolderNoOp.INSTANCE);
                                 curAddr = writeLock(curId, curPage);
 
                                 curIo = PagesListMetaIO.VERSIONS.forPage(curAddr);
@@ -325,7 +328,7 @@ public abstract class PagesList extends DataStructure {
         while (nextPageId != 0L) {
             long pageId = nextPageId;
 
-            long page = acquirePage(pageId);
+            long page = acquirePage(pageId, IoStatisticsHolderNoOp.INSTANCE);
             try {
                 long pageAddr = writeLock(pageId, page);
 
@@ -569,7 +572,7 @@ public abstract class PagesList extends DataStructure {
 
                 while (tailId != 0L) {
                     final long pageId = tailId;
-                    final long page = acquirePage(pageId);
+                    final long page = acquirePage(pageId, IoStatisticsHolderNoOp.INSTANCE);
                     try {
                         long pageAddr = readLock(pageId, page);
 
@@ -611,6 +614,7 @@ public abstract class PagesList extends DataStructure {
      * @param dataPage Data page pointer.
      * @param dataAddr Data page address.
      * @param bucket Bucket.
+     * @param statHolder Statistics holder to track IO operations.
      * @throws IgniteCheckedException If failed.
      */
     protected final void put(
@@ -618,7 +622,8 @@ public abstract class PagesList extends DataStructure {
         final long dataId,
         final long dataPage,
         final long dataAddr,
-        int bucket)
+        int bucket,
+        IoStatisticsHolder statHolder)
         throws IgniteCheckedException {
         assert bag == null ^ dataAddr == 0L;
 
@@ -635,7 +640,7 @@ public abstract class PagesList extends DataStructure {
             if (tailId == 0L)
                 continue;
 
-            final long tailPage = acquirePage(tailId);
+            final long tailPage = acquirePage(tailId, statHolder);
 
             try {
                 long tailAddr = writeLockPage(tailId, tailPage, bucket, lockAttempt++, bag); // Explicit check.
@@ -669,14 +674,14 @@ public abstract class PagesList extends DataStructure {
 
                     ok = bag != null ?
                         // Here we can always take pages from the bag to build our list.
-                        putReuseBag(tailId, tailPage, tailAddr, io, bag, bucket) :
+                        putReuseBag(tailId, tailPage, tailAddr, io, bag, bucket, statHolder) :
                         // Here we can use the data page to build list only if it is empty and
                         // it is being put into reuse bucket. Usually this will be true, but there is
                         // a case when there is no reuse bucket in the free list, but then deadlock
                         // on node page allocation from separate reuse list is impossible.
                         // If the data page is not empty it can not be put into reuse bucket and thus
                         // the deadlock is impossible as well.
-                        putDataPage(tailId, tailPage, tailAddr, io, dataId, dataPage, dataAddr, bucket);
+                        putDataPage(tailId, tailPage, tailAddr, io, dataId, dataPage, dataAddr, bucket, statHolder);
 
                     if (ok) {
                         stripe.empty = false;
@@ -703,6 +708,7 @@ public abstract class PagesList extends DataStructure {
      * @param dataPage Data page pointer.
      * @param dataAddr Data page address.
      * @param bucket Bucket.
+     * @param statHolder Statistics holder to track IO operations.
      * @return {@code true} If succeeded.
      * @throws IgniteCheckedException If failed.
      */
@@ -714,7 +720,8 @@ public abstract class PagesList extends DataStructure {
         final long dataId,
         final long dataPage,
         final long dataAddr,
-        int bucket
+        int bucket,
+        IoStatisticsHolder statHolder
     ) throws IgniteCheckedException {
         if (io.getNextId(pageAddr) != 0L)
             return false; // Splitted.
@@ -722,7 +729,7 @@ public abstract class PagesList extends DataStructure {
         int idx = io.addPage(pageAddr, dataId, pageSize());
 
         if (idx == -1)
-            handlePageFull(pageId, page, pageAddr, io, dataId, dataPage, dataAddr, bucket);
+            handlePageFull(pageId, page, pageAddr, io, dataId, dataPage, dataAddr, bucket, statHolder);
         else {
             incrementBucketSize(bucket);
 
@@ -748,6 +755,7 @@ public abstract class PagesList extends DataStructure {
      * @param data Data page pointer.
      * @param dataAddr Data page address.
      * @param bucket Bucket index.
+     * @param statHolder Statistics holder to track IO operations.
      * @throws IgniteCheckedException If failed.
      * */
     private void handlePageFull(
@@ -758,7 +766,8 @@ public abstract class PagesList extends DataStructure {
         final long dataId,
         final long data,
         final long dataAddr,
-        int bucket
+        int bucket,
+        IoStatisticsHolder statHolder
     ) throws IgniteCheckedException {
         AbstractDataPageIO dataIO = PageIO.getPageIO(dataAddr);
 
@@ -792,7 +801,7 @@ public abstract class PagesList extends DataStructure {
         else {
             // Just allocate a new node page and add our data page there.
             final long nextId = allocatePage(null);
-            final long nextPage = acquirePage(nextId);
+            final long nextPage = acquirePage(nextId, statHolder);
 
             try {
                 long nextPageAddr = writeLock(nextId, nextPage); // Newly allocated page.
@@ -849,6 +858,7 @@ public abstract class PagesList extends DataStructure {
      * @param io IO.
      * @param bag Reuse bag.
      * @param bucket Bucket.
+     * @param statHolder Statistics holder to track IO operations.
      * @return {@code true} If succeeded.
      * @throws IgniteCheckedException if failed.
      */
@@ -858,7 +868,8 @@ public abstract class PagesList extends DataStructure {
         final long pageAddr,
         PagesListNodeIO io,
         ReuseBag bag,
-        int bucket
+        int bucket,
+        IoStatisticsHolder statHolder
     ) throws IgniteCheckedException {
         assert bag != null : "bag is null";
         assert !bag.isEmpty() : "bag is empty";
@@ -884,7 +895,7 @@ public abstract class PagesList extends DataStructure {
 
                 if (idx == -1) { // Attempt to add page failed: the node page is full.
 
-                    final long nextPage = acquirePage(nextId);
+                    final long nextPage = acquirePage(nextId, statHolder);
 
                     try {
                         long nextPageAddr = writeLock(nextId, nextPage); // Page from reuse bag can't be concurrently recycled.
@@ -1038,10 +1049,12 @@ public abstract class PagesList extends DataStructure {
     /**
      * @param bucket Bucket index.
      * @param initIoVers Optional IO to initialize page.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Removed page ID.
      * @throws IgniteCheckedException If failed.
      */
-    protected final long takeEmptyPage(int bucket, @Nullable IOVersions initIoVers) throws IgniteCheckedException {
+    protected final long takeEmptyPage(int bucket, @Nullable IOVersions initIoVers,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
         for (int lockAttempt = 0; ;) {
             Stripe stripe = getPageForTake(bucket);
 
@@ -1054,7 +1067,7 @@ public abstract class PagesList extends DataStructure {
             if (tailId == 0L)
                 continue;
 
-            final long tailPage = acquirePage(tailId);
+            final long tailPage = acquirePage(tailId, statHolder);
 
             try {
                 long tailAddr = writeLockPage(tailId, tailPage, bucket, lockAttempt++, null); // Explicit check.
@@ -1115,7 +1128,7 @@ public abstract class PagesList extends DataStructure {
                             // to prevent empty page leak to data pages.
                             if (!isReuseBucket(bucket)) {
                                 if (prevId != 0L) {
-                                    Boolean ok = write(prevId, cutTail, null, bucket, FALSE);
+                                    Boolean ok = write(prevId, cutTail, null, bucket, FALSE, statHolder);
 
                                     assert ok == TRUE : ok;
 
@@ -1138,7 +1151,7 @@ public abstract class PagesList extends DataStructure {
 
                         assert prevId != 0L;
 
-                        Boolean ok = write(prevId, cutTail, bucket, FALSE);
+                        Boolean ok = write(prevId, cutTail, bucket, FALSE, statHolder);
 
                         assert ok == TRUE : ok;
 
@@ -1177,8 +1190,8 @@ public abstract class PagesList extends DataStructure {
     }
 
     /**
-     * Reused page must obtain correctly assembled page id, then initialized by proper {@link PageIO} instance
-     * and non-zero {@code itemId} of reused page id must be saved into special place.
+     * Reused page must obtain correctly assembled page id, then initialized by proper {@link PageIO} instance and
+     * non-zero {@code itemId} of reused page id must be saved into special place.
      *
      * @param reusedPageId Reused page id.
      * @param reusedPage Reused page.
@@ -1187,6 +1200,7 @@ public abstract class PagesList extends DataStructure {
      * @param flag Flag.
      * @param initIo Initial io.
      * @return Prepared page id.
+     * @throws IgniteCheckedException In case of failure.
      */
     protected final long initReusedPage(long reusedPageId, long reusedPage, long reusedPageAddr,
         int partId, byte flag, PageIO initIo) throws IgniteCheckedException {
@@ -1220,21 +1234,23 @@ public abstract class PagesList extends DataStructure {
      * @param dataAddr Data page address.
      * @param dataIO Data page IO.
      * @param bucket Bucket index.
-     * @throws IgniteCheckedException If failed.
+     * @param statHolder Statistics holder to track IO operations.
      * @return {@code True} if page was removed.
+     * @throws IgniteCheckedException If failed.
      */
     protected final boolean removeDataPage(
         final long dataId,
         final long dataPage,
         final long dataAddr,
         AbstractDataPageIO dataIO,
-        int bucket)
+        int bucket,
+        IoStatisticsHolder statHolder)
         throws IgniteCheckedException {
         final long pageId = dataIO.getFreeListPageId(dataAddr);
 
         assert pageId != 0;
 
-        final long page = acquirePage(pageId);
+        final long page = acquirePage(pageId, statHolder);
         try {
             long nextId;
 
@@ -1277,7 +1293,7 @@ public abstract class PagesList extends DataStructure {
                 if (nextId == 0L) {
                     long prevId = io.getPreviousId(pageAddr);
 
-                    recycleId = mergeNoNext(pageId, page, pageAddr, prevId, bucket);
+                    recycleId = mergeNoNext(pageId, page, pageAddr, prevId, bucket, statHolder);
                 }
             }
             finally {
@@ -1286,7 +1302,7 @@ public abstract class PagesList extends DataStructure {
 
             // Perform a fair merge after lock release (to have a correct locking order).
             if (nextId != 0L)
-                recycleId = merge(pageId, page, nextId, bucket);
+                recycleId = merge(pageId, page, nextId, bucket, statHolder);
 
             if (recycleId != 0L)
                 reuseList.addForRecycle(new SingletonReuseBag(recycleId));
@@ -1304,6 +1320,7 @@ public abstract class PagesList extends DataStructure {
      * @param pageAddr Page address.
      * @param prevId Previous page ID.
      * @param bucket Bucket index.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Page ID to recycle.
      * @throws IgniteCheckedException If failed.
      */
@@ -1312,7 +1329,8 @@ public abstract class PagesList extends DataStructure {
         long page,
         long pageAddr,
         long prevId,
-        int bucket)
+        int bucket,
+        IoStatisticsHolder statHolder)
         throws IgniteCheckedException {
         // If we do not have a next page (we are tail) and we are on reuse bucket,
         // then we can leave as is as well, because it is normal to have an empty tail page here.
@@ -1320,7 +1338,7 @@ public abstract class PagesList extends DataStructure {
             return 0L;
 
         if (prevId != 0L) { // Cut tail if we have a previous page.
-            Boolean ok = write(prevId, cutTail, null, bucket, FALSE);
+            Boolean ok = write(prevId, cutTail, null, bucket, FALSE, statHolder);
 
             assert ok == TRUE: ok;
         }
@@ -1340,6 +1358,7 @@ public abstract class PagesList extends DataStructure {
      * @param page Page pointer.
      * @param nextId Next page ID.
      * @param bucket Bucket index.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Page ID to recycle.
      * @throws IgniteCheckedException If failed.
      */
@@ -1347,14 +1366,15 @@ public abstract class PagesList extends DataStructure {
         final long pageId,
         final long page,
         long nextId,
-        int bucket)
+        int bucket,
+        IoStatisticsHolder statHolder)
         throws IgniteCheckedException {
         assert nextId != 0; // We should do mergeNoNext then.
 
         // Lock all the pages in correct order (from next to previous) and do the merge in retry loop.
         for (;;) {
             final long curId = nextId;
-            final long curPage = curId == 0L ? 0L : acquirePage(curId);
+            final long curPage = curId == 0L ? 0L : acquirePage(curId, statHolder);
             try {
                 boolean write = false;
 
@@ -1376,7 +1396,7 @@ public abstract class PagesList extends DataStructure {
 
                     // Check if we see a consistent state of the world.
                     if (io.getNextId(pageAddr) == curId && (curId == 0L) == (curAddr == 0L)) {
-                        long recycleId = doMerge(pageId, page, pageAddr, io, curId, curPage, curAddr, bucket);
+                        long recycleId = doMerge(pageId, page, pageAddr, io, curId, curPage, curAddr, bucket, statHolder);
 
                         write = true;
 
@@ -1409,6 +1429,7 @@ public abstract class PagesList extends DataStructure {
      * @param nextPage Next page absolute pointer.
      * @param nextAddr Next page address.
      * @param bucket Bucket index.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Page to recycle.
      * @throws IgniteCheckedException If failed.
      */
@@ -1420,12 +1441,13 @@ public abstract class PagesList extends DataStructure {
         long nextId,
         long nextPage,
         long nextAddr,
-        int bucket
+        int bucket,
+        IoStatisticsHolder statHolder
     ) throws IgniteCheckedException {
         long prevId = io.getPreviousId(pageAddr);
 
         if (nextId == 0L)
-            return mergeNoNext(pageId, page, pageAddr, prevId, bucket);
+            return mergeNoNext(pageId, page, pageAddr, prevId, bucket, statHolder);
         else {
             // No one must be able to merge it while we keep a reference.
             assert getPageId(nextAddr) == nextId;
@@ -1441,7 +1463,7 @@ public abstract class PagesList extends DataStructure {
                     wal.log(new PagesListSetPreviousRecord(grpId, nextId, 0L));
             }
             else // Do a fair merge: link previous and next to each other.
-                fairMerge(prevId, pageId, nextId, nextPage, nextAddr);
+                fairMerge(prevId, pageId, nextId, nextPage, nextAddr, statHolder);
 
             return recyclePage(pageId, page, pageAddr, null);
         }
@@ -1454,6 +1476,7 @@ public abstract class PagesList extends DataStructure {
      * @param nextId Next page ID.
      * @param nextPage Next page absolute pointer.
      * @param nextAddr Next page address.
+     * @param statHolder Statistics holder to track IO operations.
      * @throws IgniteCheckedException If failed.
      */
     private void fairMerge(
@@ -1461,9 +1484,10 @@ public abstract class PagesList extends DataStructure {
         long pageId,
         long nextId,
         long nextPage,
-        long nextAddr)
+        long nextAddr,
+        IoStatisticsHolder statHolder)
         throws IgniteCheckedException {
-        long prevPage = acquirePage(prevId);
+        long prevPage = acquirePage(prevId, statHolder);
 
         try {
             final long prevAddr = writeLock(prevId, prevPage); // No check, we keep a reference.

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageRowStore.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageRowStore.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageRowStore.java
index e4bafd5..c482a94 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageRowStore.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/metastorage/MetastorageRowStore.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.cache.persistence.metastorage;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.processors.cache.persistence.IgniteCacheDatabaseSharedManager;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.FreeList;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 
 /**
  *
@@ -54,7 +55,7 @@ public class MetastorageRowStore {
         db.checkpointReadLock();
 
         try {
-            freeList.removeDataRowByLink(link);
+            freeList.removeDataRowByLink(link, IoStatisticsHolderNoOp.INSTANCE);
         }
         finally {
             db.checkpointReadUnlock();
@@ -69,7 +70,7 @@ public class MetastorageRowStore {
         db.checkpointReadLock();
 
         try {
-            freeList.insertDataRow(row);
+            freeList.insertDataRow(row, IoStatisticsHolderNoOp.INSTANCE);
         }
         finally {
             db.checkpointReadUnlock();
@@ -83,7 +84,7 @@ public class MetastorageRowStore {
      * @throws IgniteCheckedException If failed.
      */
     public boolean updateRow(long link, MetastorageDataRow row) throws IgniteCheckedException {
-        return freeList.updateDataRow(link, row);
+        return freeList.updateDataRow(link, row, IoStatisticsHolderNoOp.INSTANCE);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryEx.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryEx.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryEx.java
index af204dd..9e15c8d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryEx.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryEx.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.IgniteInternalFuture;
 import org.apache.ignite.internal.pagemem.FullPageId;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.processors.cache.persistence.StorageException;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 import org.apache.ignite.internal.util.GridMultiCollectionWrapper;
 import org.jetbrains.annotations.Nullable;
 
@@ -75,7 +76,7 @@ public interface PageMemoryEx extends PageMemory {
 
     /**
      * @see #acquirePage(int, long)
-     * Will not read page from file if it is not present in memory
+     * Will read page from file if it is not present in memory
      *
      * @param grpId Cache group ID.
      * @param pageId Page id.
@@ -84,7 +85,8 @@ public interface PageMemoryEx extends PageMemory {
      * @throws StorageException If page reading failed from storage.
      * @return Page.
      */
-    public long acquirePage(int grpId, long pageId, boolean restore) throws IgniteCheckedException;
+    public long acquirePage(int grpId, long pageId, IoStatisticsHolder statHldr,
+        boolean restore) throws IgniteCheckedException;
 
     /**
      * Heuristic method which allows a thread to check if it safe to start memory struture modifications

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImpl.java
index c4b0104..ed55854 100755
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/pagemem/PageMemoryImpl.java
@@ -74,6 +74,8 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageParti
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.TrackingPageIO;
 import org.apache.ignite.internal.processors.cache.persistence.wal.crc.IgniteDataIntegrityViolationException;
 import org.apache.ignite.internal.processors.query.GridQueryRowCacheCleaner;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 import org.apache.ignite.internal.util.GridConcurrentHashSet;
 import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.GridMultiCollectionWrapper;
@@ -644,11 +646,18 @@ public class PageMemoryImpl implements PageMemoryEx {
 
     /** {@inheritDoc} */
     @Override public long acquirePage(int grpId, long pageId) throws IgniteCheckedException {
-        return acquirePage(grpId, pageId, false);
+        return acquirePage(grpId, pageId, IoStatisticsHolderNoOp.INSTANCE, false);
     }
 
     /** {@inheritDoc} */
-    @Override public long acquirePage(int grpId, long pageId, boolean restore) throws IgniteCheckedException {
+    @Override public long acquirePage(int grpId, long pageId,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
+        return acquirePage(grpId, pageId, statHolder, false);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long acquirePage(int grpId, long pageId, IoStatisticsHolder statHolder,
+        boolean restore) throws IgniteCheckedException {
         FullPageId fullId = new FullPageId(pageId, grpId);
 
         int partId = PageIdUtils.partId(pageId);
@@ -672,6 +681,8 @@ public class PageMemoryImpl implements PageMemoryEx {
 
                 seg.acquirePage(absPtr);
 
+                statHolder.trackLogicalRead(absPtr + PAGE_OVERHEAD);
+
                 return absPtr;
             }
         }
@@ -758,7 +769,7 @@ public class PageMemoryImpl implements PageMemoryEx {
 
                 long pageAddr = absPtr + PAGE_OVERHEAD;
 
-                GridUnsafe.setMemory(absPtr + PAGE_OVERHEAD, pageSize(), (byte)0);
+                GridUnsafe.setMemory(pageAddr, pageSize(), (byte)0);
 
                 PageHeader.fullPageId(absPtr, fullId);
                 PageHeader.writeTimestamp(absPtr, U.currentTimeMillis());
@@ -775,6 +786,9 @@ public class PageMemoryImpl implements PageMemoryEx {
 
             seg.acquirePage(absPtr);
 
+            if(!readPageFromStore)
+                statHolder.trackLogicalRead(absPtr + PAGE_OVERHEAD);
+
             return absPtr;
         }
         catch (IgniteOutOfMemoryException oom) {
@@ -803,6 +817,8 @@ public class PageMemoryImpl implements PageMemoryEx {
                 try {
                     storeMgr.read(grpId, pageId, buf);
 
+                    statHolder.trackPhysicalAndLogicalRead(pageAddr);
+
                     actualPageId = PageIO.getPageId(buf);
 
                     memMetrics.onPageRead();
@@ -815,6 +831,8 @@ public class PageMemoryImpl implements PageMemoryEx {
 
                     tryToRestorePage(fullId, buf);
 
+                    statHolder.trackPhysicalAndLogicalRead(pageAddr);
+
                     memMetrics.onPageRead();
                 }
                 finally {

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/BPlusTree.java
----------------------------------------------------------------------
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 476667a..7b07dcf 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
@@ -59,6 +59,8 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseL
 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.failure.FailureProcessor;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 import org.apache.ignite.internal.util.GridArrays;
 import org.apache.ignite.internal.util.GridLongList;
 import org.apache.ignite.internal.util.IgniteTree;
@@ -640,7 +642,9 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
      */
     private class CutRoot extends PageHandler<Void, Bool> {
         /** {@inheritDoc} */
-        @Override public Bool run(int cacheId, long metaId, long metaPage, long metaAddr, PageIO iox, Boolean walPlc, Void ignore, int lvl)
+        @Override public Bool run(int cacheId, long metaId, long metaPage, long metaAddr, PageIO iox, Boolean walPlc,
+            Void ignore, int lvl,
+            IoStatisticsHolder statHolder)
             throws IgniteCheckedException {
             // Safe cast because we should never recycle meta page until the tree is destroyed.
             BPlusMetaIO io = (BPlusMetaIO)iox;
@@ -670,7 +674,9 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
      */
     private class AddRoot extends PageHandler<Long, Bool> {
         /** {@inheritDoc} */
-        @Override public Bool run(int cacheId, long metaId, long metaPage, long pageAddr, PageIO iox, Boolean walPlc, Long rootPageId, int lvl)
+        @Override public Bool run(int cacheId, long metaId, long metaPage, long pageAddr, PageIO iox, Boolean walPlc,
+            Long rootPageId, int lvl,
+            IoStatisticsHolder statHolder)
             throws IgniteCheckedException {
             assert rootPageId != null;
 
@@ -701,7 +707,9 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
      */
     private class InitRoot extends PageHandler<Long, Bool> {
         /** {@inheritDoc} */
-        @Override public Bool run(int cacheId, long metaId, long metaPage, long pageAddr, PageIO iox, Boolean walPlc, Long rootId, int inlineSize)
+        @Override public Bool run(int cacheId, long metaId, long metaPage, long pageAddr, PageIO iox, Boolean walPlc,
+            Long rootId, int inlineSize,
+            IoStatisticsHolder statHolder)
             throws IgniteCheckedException {
             assert rootId != null;
 
@@ -847,7 +855,8 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
             init(rootId, latestLeafIO());
 
             // Initialize meta page with new root page.
-            Bool res = write(metaPageId, initRoot, BPlusMetaIO.VERSIONS.latest(), rootId, inlineSize, FALSE);
+            Bool res = write(metaPageId, initRoot, BPlusMetaIO.VERSIONS.latest(), rootId, inlineSize, FALSE,
+                statisticsHolder());
 
             assert res == TRUE: res;
 
@@ -3546,7 +3555,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
                             releasePage(newRootId, newRootPage);
                         }
 
-                        Bool res = write(metaPageId, addRoot, newRootId, lvl + 1, FALSE);
+                        Bool res = write(metaPageId, addRoot, newRootId, lvl + 1, FALSE, statisticsHolder());
 
                         assert res == TRUE : res;
 
@@ -3586,7 +3595,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
                 this.fwdId = fwdId;
                 this.pageId = pageId;
 
-                Result res = write(pageId, page, replace, this, lvl, RETRY);
+                Result res = write(pageId, page, replace, this, lvl, RETRY, statisticsHolder());
 
                 // Restore args.
                 this.pageId = oldPageId;
@@ -3616,7 +3625,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
             this.pageId = pageId;
             this.fwdId = fwdId;
 
-            return write(pageId, page, insert, this, lvl, RETRY);
+            return write(pageId, page, insert, this, lvl, RETRY, statisticsHolder());
         }
 
         /**
@@ -3632,7 +3641,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
             this.pageId = pageId;
             this.fwdId = fwdId;
 
-            return write(pageId, page, replace, this, lvl, RETRY);
+            return write(pageId, page, replace, this, lvl, RETRY, statisticsHolder());
         }
 
         /** {@inheritDoc} */
@@ -4307,7 +4316,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
             long backPage = acquirePage(backId);
 
             try {
-                return write(backId, backPage, lockBackAndRmvFromLeaf, this, 0, RETRY);
+                return write(backId, backPage, lockBackAndRmvFromLeaf, this, 0, RETRY, statisticsHolder());
             }
             finally {
                 if (canRelease(backId, 0))
@@ -4322,7 +4331,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
         private Result doRemoveFromLeaf() throws IgniteCheckedException {
             assert page != 0L;
 
-            return write(pageId, page, rmvFromLeaf, this, 0, RETRY);
+            return write(pageId, page, rmvFromLeaf, this, 0, RETRY, statisticsHolder());
         }
 
         /**
@@ -4333,7 +4342,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
         private Result doLockTail(int lvl) throws IgniteCheckedException {
             assert page != 0L;
 
-            return write(pageId, page, lockTail, this, lvl, RETRY);
+            return write(pageId, page, lockTail, this, lvl, RETRY, statisticsHolder());
         }
 
         /**
@@ -4361,7 +4370,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
             long backPage = acquirePage(backId);
 
             try {
-                return write(backId, backPage, lockBackAndTail, this, lvl, RETRY);
+                return write(backId, backPage, lockBackAndTail, this, lvl, RETRY, statisticsHolder());
             }
             finally {
                 if (canRelease(backId, lvl))
@@ -4382,7 +4391,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
             long fwdPage = acquirePage(fwdId);
 
             try {
-                return write(fwdId, fwdPage, lockTailForward, this, lvl, RETRY);
+                return write(fwdId, fwdPage, lockTailForward, this, lvl, RETRY, statisticsHolder());
             }
             finally {
                 // If we were not able to lock forward page as tail, release the page.
@@ -4625,7 +4634,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
          * @throws IgniteCheckedException If failed.
          */
         private void cutRoot(int lvl) throws IgniteCheckedException {
-            Bool res = write(metaPageId, cutRoot, lvl, FALSE);
+            Bool res = write(metaPageId, cutRoot, lvl, FALSE, statisticsHolder());
 
             assert res == TRUE : res;
         }
@@ -5474,9 +5483,8 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
         private void iterate() throws IgniteCheckedException {
             find();
 
-            if (nextPageId == 0) {
+            if (nextPageId == 0)
                 return;
-            }
 
             for (;;) {
                 L lastRow0 = lastRow;
@@ -5637,7 +5645,7 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
         /** {@inheritDoc} */
         @SuppressWarnings("unchecked")
         @Override public Result run(int cacheId, long pageId, long page, long pageAddr, PageIO iox, Boolean walPlc,
-            G g, int lvl) throws IgniteCheckedException {
+            G g, int lvl, IoStatisticsHolder statHolder) throws IgniteCheckedException {
             assert PageIO.getPageId(pageAddr) == pageId;
 
             // If we've passed the check for correct page ID, we can safely cast.
@@ -5790,4 +5798,58 @@ public abstract class BPlusTree<L, T extends L> extends DataStructure implements
     protected int getLockRetries() {
         return LOCK_RETRIES;
     }
+
+    /**
+     * @param pageId Page ID.
+     * @return Page absolute pointer.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected final long acquirePage(long pageId) throws IgniteCheckedException {
+        return acquirePage(pageId, statisticsHolder());
+    }
+
+    /**
+     * @param pageId Page ID.
+     * @param h Handler.
+     * @param arg Argument.
+     * @param intArg Argument of type {@code int}.
+     * @param lockFailed Result in case of lock failure due to page recycling.
+     * @return Handler result.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected final <X, R> R read(
+        long pageId,
+        PageHandler<X, R> h,
+        X arg,
+        int intArg,
+        R lockFailed) throws IgniteCheckedException {
+        return read(pageId, h, arg, intArg, lockFailed, statisticsHolder());
+    }
+
+    /**
+     * @param pageId Page ID.
+     * @param page Page pointer.
+     * @param h Handler.
+     * @param arg Argument.
+     * @param intArg Argument of type {@code int}.
+     * @param lockFailed Result in case of lock failure due to page recycling.
+     * @return Handler result.
+     * @throws IgniteCheckedException If failed.
+     */
+    protected final <X, R> R read(
+        long pageId,
+        long page,
+        PageHandler<X, R> h,
+        X arg,
+        int intArg,
+        R lockFailed) throws IgniteCheckedException {
+        return read(pageId, page, h, arg, intArg, lockFailed, statisticsHolder());
+    }
+
+    /**
+     * @return Statistics holder to track IO operations.
+     */
+    protected IoStatisticsHolder statisticsHolder() {
+        return IoStatisticsHolderNoOp.INSTANCE;
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java
index 85a1e8c..56ebb0f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/io/PageIO.java
@@ -21,7 +21,6 @@ import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
 import org.apache.ignite.IgniteCheckedException;
-import org.apache.ignite.spi.encryption.EncryptionSpi;
 import org.apache.ignite.internal.pagemem.PageIdUtils;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.PageUtils;
@@ -36,17 +35,20 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHan
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageLockListener;
 import org.apache.ignite.internal.processors.cache.tree.CacheIdAwareDataInnerIO;
 import org.apache.ignite.internal.processors.cache.tree.CacheIdAwareDataLeafIO;
-import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccCacheIdAwareDataInnerIO;
-import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccCacheIdAwareDataLeafIO;
 import org.apache.ignite.internal.processors.cache.tree.CacheIdAwarePendingEntryInnerIO;
 import org.apache.ignite.internal.processors.cache.tree.CacheIdAwarePendingEntryLeafIO;
 import org.apache.ignite.internal.processors.cache.tree.DataInnerIO;
 import org.apache.ignite.internal.processors.cache.tree.DataLeafIO;
-import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataInnerIO;
-import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataLeafIO;
 import org.apache.ignite.internal.processors.cache.tree.PendingEntryInnerIO;
 import org.apache.ignite.internal.processors.cache.tree.PendingEntryLeafIO;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccCacheIdAwareDataInnerIO;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccCacheIdAwareDataLeafIO;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataInnerIO;
+import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataLeafIO;
+import org.apache.ignite.internal.stat.IndexPageType;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 import org.apache.ignite.internal.util.GridStringBuilder;
+import org.apache.ignite.spi.encryption.EncryptionSpi;
 
 /**
  * Base format for all the page types.
@@ -78,7 +80,7 @@ import org.apache.ignite.internal.util.GridStringBuilder;
  *
  * 7. It is almost always preferable to read or write (especially write) page contents using
  *    static methods on {@link PageHandler}. To just initialize new page use
- *    {@link PageHandler#initPage(PageMemory, int, long, PageIO, IgniteWriteAheadLogManager, PageLockListener)}
+ *    {@link PageHandler#initPage(PageMemory, int, long, PageIO, IgniteWriteAheadLogManager, PageLockListener, IoStatisticsHolder)}
  *    method with needed IO instance.
  */
 public abstract class PageIO {
@@ -778,6 +780,44 @@ public abstract class PageIO {
     }
 
     /**
+     * @param pageAddr Address of page.
+     * @return Index page type.
+     */
+    public static IndexPageType deriveIndexPageType(long pageAddr) {
+        int pageIoType = PageIO.getType(pageAddr);
+        switch (pageIoType) {
+            case PageIO.T_DATA_REF_INNER:
+            case PageIO.T_DATA_REF_MVCC_INNER:
+            case PageIO.T_H2_REF_INNER:
+            case PageIO.T_H2_MVCC_REF_INNER:
+            case PageIO.T_CACHE_ID_AWARE_DATA_REF_INNER:
+            case PageIO.T_CACHE_ID_DATA_REF_MVCC_INNER:
+                return IndexPageType.INNER;
+
+            case PageIO.T_DATA_REF_LEAF:
+            case PageIO.T_DATA_REF_MVCC_LEAF:
+            case PageIO.T_H2_REF_LEAF:
+            case PageIO.T_H2_MVCC_REF_LEAF:
+            case PageIO.T_CACHE_ID_AWARE_DATA_REF_LEAF:
+            case PageIO.T_CACHE_ID_DATA_REF_MVCC_LEAF:
+                return IndexPageType.LEAF;
+
+            default:
+                if ((PageIO.T_H2_EX_REF_LEAF_START <= pageIoType && pageIoType <= PageIO.T_H2_EX_REF_LEAF_END) ||
+                    (PageIO.T_H2_EX_REF_MVCC_LEAF_START <= pageIoType && pageIoType <= PageIO.T_H2_EX_REF_MVCC_LEAF_END)
+                )
+                    return IndexPageType.LEAF;
+
+                if ((PageIO.T_H2_EX_REF_INNER_START <= pageIoType && pageIoType <= PageIO.T_H2_EX_REF_INNER_END) ||
+                    (PageIO.T_H2_EX_REF_MVCC_INNER_START <= pageIoType && pageIoType <= PageIO.T_H2_EX_REF_MVCC_INNER_END)
+                )
+                    return IndexPageType.INNER;
+        }
+
+        return IndexPageType.NOT_INDEX;
+    }
+
+    /**
      * @param type Type to test.
      * @return {@code True} if data page.
      */

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java
index 6291c1d..984a957 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/reuse/ReuseListImpl.java
@@ -22,6 +22,7 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.PagesList;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 
 /**
  * Reuse list.
@@ -65,12 +66,12 @@ public class ReuseListImpl extends PagesList implements ReuseList {
 
     /** {@inheritDoc} */
     @Override public void addForRecycle(ReuseBag bag) throws IgniteCheckedException {
-        put(bag, 0, 0, 0, 0);
+        put(bag, 0, 0, 0, 0, IoStatisticsHolderNoOp.INSTANCE);
     }
 
     /** {@inheritDoc} */
     @Override public long takeRecycledPage() throws IgniteCheckedException {
-        return takeEmptyPage(0, null);
+        return takeEmptyPage(0, null, IoStatisticsHolderNoOp.INSTANCE);
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/PageHandler.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/PageHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/PageHandler.java
index 1135868..5ab1bf3 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/PageHandler.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/tree/util/PageHandler.java
@@ -24,6 +24,7 @@ import org.apache.ignite.internal.pagemem.PageSupport;
 import org.apache.ignite.internal.pagemem.wal.IgniteWriteAheadLogManager;
 import org.apache.ignite.internal.pagemem.wal.record.delta.InitNewPageRecord;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 import org.apache.ignite.internal.util.GridUnsafe;
 
 import static java.lang.Boolean.FALSE;
@@ -37,7 +38,8 @@ public abstract class PageHandler<X, R> {
     private static final PageHandler<Void, Boolean> NO_OP = new PageHandler<Void, Boolean>() {
         @Override public Boolean run(int cacheId, long pageId, long page, long pageAddr, PageIO io, Boolean walPlc,
             Void arg,
-            int intArg) throws IgniteCheckedException {
+            int intArg,
+            IoStatisticsHolder statHolder) throws IgniteCheckedException {
             return Boolean.TRUE;
         }
     };
@@ -51,6 +53,7 @@ public abstract class PageHandler<X, R> {
      * @param walPlc Full page WAL record policy.
      * @param arg Argument.
      * @param intArg Argument of type {@code int}.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Result.
      * @throws IgniteCheckedException If failed.
      */
@@ -62,7 +65,9 @@ public abstract class PageHandler<X, R> {
         PageIO io,
         Boolean walPlc,
         X arg,
-        int intArg)
+        int intArg,
+        IoStatisticsHolder statHolder
+    )
         throws IgniteCheckedException;
 
     /**
@@ -93,6 +98,7 @@ public abstract class PageHandler<X, R> {
      * @param arg Argument.
      * @param intArg Argument of type {@code int}.
      * @param lockFailed Result in case of lock failure due to page recycling.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Handler result.
      * @throws IgniteCheckedException If failed.
      */
@@ -104,9 +110,10 @@ public abstract class PageHandler<X, R> {
         PageHandler<X, R> h,
         X arg,
         int intArg,
-        R lockFailed
+        R lockFailed,
+        IoStatisticsHolder statHolder
     ) throws IgniteCheckedException {
-        long page = pageMem.acquirePage(cacheId, pageId);
+        long page = pageMem.acquirePage(cacheId, pageId, statHolder);
         try {
             long pageAddr = readLock(pageMem, cacheId, pageId, page, lsnr);
 
@@ -114,7 +121,7 @@ public abstract class PageHandler<X, R> {
                 return lockFailed;
             try {
                 PageIO io = PageIO.getPageIO(pageAddr);
-                return h.run(cacheId, pageId, page, pageAddr, io, null, arg, intArg);
+                return h.run(cacheId, pageId, page, pageAddr, io, null, arg, intArg, statHolder);
             }
             finally {
                 readUnlock(pageMem, cacheId, pageId, page, pageAddr, lsnr);
@@ -135,6 +142,7 @@ public abstract class PageHandler<X, R> {
      * @param arg Argument.
      * @param intArg Argument of type {@code int}.
      * @param lockFailed Result in case of lock failure due to page recycling.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Handler result.
      * @throws IgniteCheckedException If failed.
      */
@@ -147,7 +155,8 @@ public abstract class PageHandler<X, R> {
         PageHandler<X, R> h,
         X arg,
         int intArg,
-        R lockFailed
+        R lockFailed,
+        IoStatisticsHolder statHolder
     ) throws IgniteCheckedException {
         long pageAddr = 0L;
 
@@ -156,7 +165,7 @@ public abstract class PageHandler<X, R> {
                 return lockFailed;
 
             PageIO io = PageIO.getPageIO(pageAddr);
-            return h.run(cacheId, pageId, page, pageAddr, io, null, arg, intArg);
+            return h.run(cacheId, pageId, page, pageAddr, io, null, arg, intArg, statHolder);
         }
         finally {
             if (pageAddr != 0L)
@@ -214,6 +223,7 @@ public abstract class PageHandler<X, R> {
      * @param init IO for new page initialization.
      * @param wal Write ahead log.
      * @param lsnr Lock listener.
+     * @param statHolder Statistics holder to track IO operations.
      * @throws IgniteCheckedException If failed.
      */
     public static void initPage(
@@ -222,9 +232,10 @@ public abstract class PageHandler<X, R> {
         long pageId,
         PageIO init,
         IgniteWriteAheadLogManager wal,
-        PageLockListener lsnr
+        PageLockListener lsnr,
+        IoStatisticsHolder statHolder
     ) throws IgniteCheckedException {
-        Boolean res = writePage(pageMem, grpId, pageId, lsnr, PageHandler.NO_OP, init, wal, null, null, 0, FALSE);
+        Boolean res = writePage(pageMem, grpId, pageId, lsnr, PageHandler.NO_OP, init, wal, null, null, 0, FALSE, statHolder);
 
         assert res != FALSE;
     }
@@ -241,6 +252,7 @@ public abstract class PageHandler<X, R> {
      * @param arg Argument.
      * @param intArg Argument of type {@code int}.
      * @param lockFailed Result in case of lock failure due to page recycling.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Handler result.
      * @throws IgniteCheckedException If failed.
      */
@@ -255,10 +267,11 @@ public abstract class PageHandler<X, R> {
         Boolean walPlc,
         X arg,
         int intArg,
-        R lockFailed
+        R lockFailed,
+        IoStatisticsHolder statHolder
     ) throws IgniteCheckedException {
         boolean releaseAfterWrite = true;
-        long page = pageMem.acquirePage(grpId, pageId);
+        long page = pageMem.acquirePage(grpId, pageId, statHolder);
         try {
             long pageAddr = writeLock(pageMem, grpId, pageId, page, lsnr, false);
 
@@ -276,7 +289,7 @@ public abstract class PageHandler<X, R> {
                 else
                     init = PageIO.getPageIO(pageAddr);
 
-                R res = h.run(grpId, pageId, page, pageAddr, init, walPlc, arg, intArg);
+                R res = h.run(grpId, pageId, page, pageAddr, init, walPlc, arg, intArg, statHolder);
 
                 ok = true;
 
@@ -308,6 +321,7 @@ public abstract class PageHandler<X, R> {
      * @param arg Argument.
      * @param intArg Argument of type {@code int}.
      * @param lockFailed Result in case of lock failure due to page recycling.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Handler result.
      * @throws IgniteCheckedException If failed.
      */
@@ -323,7 +337,8 @@ public abstract class PageHandler<X, R> {
         Boolean walPlc,
         X arg,
         int intArg,
-        R lockFailed
+        R lockFailed,
+        IoStatisticsHolder statHolder
     ) throws IgniteCheckedException {
         long pageAddr = writeLock(pageMem, grpId, pageId, page, lsnr, false);
 
@@ -341,7 +356,7 @@ public abstract class PageHandler<X, R> {
             else
                 init = PageIO.getPageIO(pageAddr);
 
-            R res = h.run(grpId, pageId, page, pageAddr, init, walPlc, arg, intArg);
+            R res = h.run(grpId, pageId, page, pageAddr, init, walPlc, arg, intArg, statHolder);
 
             ok = true;
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java
index 03bac2b..e56b05b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/wal/reader/StandaloneGridKernalContext.java
@@ -85,6 +85,7 @@ import org.apache.ignite.internal.processors.session.GridTaskSessionProcessor;
 import org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
 import org.apache.ignite.internal.processors.task.GridTaskProcessor;
 import org.apache.ignite.internal.processors.timeout.GridTimeoutProcessor;
+import org.apache.ignite.internal.stat.IoStatisticsManager;
 import org.apache.ignite.internal.suggestions.GridPerformanceSuggestions;
 import org.apache.ignite.internal.util.IgniteExceptionRegistry;
 import org.apache.ignite.internal.util.StripedExecutor;
@@ -648,6 +649,10 @@ public class StandaloneGridKernalContext implements GridKernalContext {
         return null;
     }
 
+    @Override public IoStatisticsManager ioStats() {
+        return null;
+    }
+
     /** {@inheritDoc} */
     @Override public Thread.UncaughtExceptionHandler uncaughtExceptionHandler() {
         return null;

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java
index 4226ec4..eba0e7f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/tree/CacheDataTree.java
@@ -36,6 +36,7 @@ import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccCacheIdAwa
 import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataInnerIO;
 import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataLeafIO;
 import org.apache.ignite.internal.processors.cache.tree.mvcc.data.MvccDataRow;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 import org.apache.ignite.internal.util.GridUnsafe;
 import org.apache.ignite.internal.util.typedef.internal.CU;
 
@@ -200,6 +201,11 @@ public class CacheDataTree extends BPlusTree<CacheSearchRow, CacheDataRow> {
             return rowStore.dataRow(cacheId, hash, link, x);
     }
 
+    /** {@inheritDoc} */
+    @Override protected IoStatisticsHolder statisticsHolder() {
+        return grp.statisticsHolderIdx();
+    }
+
     /**
      * @param key Key.
      * @param link Link.

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/stat/IndexPageType.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/stat/IndexPageType.java b/modules/core/src/main/java/org/apache/ignite/internal/stat/IndexPageType.java
new file mode 100644
index 0000000..a64c5f7
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/stat/IndexPageType.java
@@ -0,0 +1,33 @@
+/*
+ * 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.stat;
+
+/**
+ * Enumeration of types of b-tree index pages.
+ */
+public enum IndexPageType {
+    /** Leaf B-tree index page. */
+    LEAF,
+
+    /** Inner B-tree index page. */
+    INNER,
+
+    /** Not index page. */
+    NOT_INDEX;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolder.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolder.java b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolder.java
new file mode 100644
index 0000000..0c36820
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolder.java
@@ -0,0 +1,66 @@
+/*
+ * 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.stat;
+
+import java.util.Map;
+
+/**
+ * Holder of IO statistics.
+ */
+public interface IoStatisticsHolder {
+
+    /**
+     * Track logical read of given page.
+     *
+     * @param pageAddr Address of page.
+     */
+    public void trackLogicalRead(long pageAddr);
+
+    /**
+     * Track physical and logical read of given page.
+     *
+     * @param pageAddr start address of page.
+     */
+    public void trackPhysicalAndLogicalRead(long pageAddr);
+
+    /**
+     * @return Number of logical reads.
+     */
+    public long logicalReads();
+
+    /**
+     * @return Number of physical reads.
+     */
+    public long physicalReads();
+
+    /**
+     * @return Logical reads statistics per page types.
+     */
+    public Map<String, Long> logicalReadsMap();
+
+    /**
+     * @return Physical reads statistics per page types.
+     */
+    public Map<String, Long> physicalReadsMap();
+
+    /**
+     * Reset statistics. All counters will be reset to 0.
+     */
+    public void resetStatistics();
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderCache.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderCache.java b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderCache.java
new file mode 100644
index 0000000..415db99
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderCache.java
@@ -0,0 +1,121 @@
+/*
+ * 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.stat;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.LongAdder;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Cache statistics holder to gather statistics related to concrete cache.
+ */
+public class IoStatisticsHolderCache implements IoStatisticsHolder {
+    /** */
+    private static final String PHYSICAL_READS = "PHYSICAL_READS";
+
+    /** */
+    private static final String LOGICAL_READS = "LOGICAL_READS";
+
+    /** */
+    private LongAdder logicalReadCtr = new LongAdder();
+
+    /** */
+    private LongAdder physicalReadCtr = new LongAdder();
+
+    /** */
+    private final String cacheName;
+
+    /**
+     * @param cacheName Name of cache.
+     */
+    public IoStatisticsHolderCache(String cacheName) {
+        assert cacheName != null;
+
+        this.cacheName = cacheName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void trackLogicalRead(long pageAddr) {
+        int pageIoType = PageIO.getType(pageAddr);
+
+        if (pageIoType == PageIO.T_DATA) {
+            logicalReadCtr.increment();
+
+            IoStatisticsQueryHelper.trackLogicalReadQuery(pageAddr);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    @Override public void trackPhysicalAndLogicalRead(long pageAddr) {
+        int pageIoType = PageIO.getType(pageAddr);
+
+        if (pageIoType == PageIO.T_DATA) {
+            logicalReadCtr.increment();
+
+            physicalReadCtr.increment();
+
+            IoStatisticsQueryHelper.trackPhysicalAndLogicalReadQuery(pageAddr);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public long logicalReads() {
+        return logicalReadCtr.longValue();
+    }
+
+    /** {@inheritDoc} */
+    @Override public long physicalReads() {
+        return physicalReadCtr.longValue();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<String, Long> logicalReadsMap() {
+        Map<String, Long> res = new HashMap<>(2);
+
+        res.put(LOGICAL_READS, logicalReads());
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<String, Long> physicalReadsMap() {
+        Map<String, Long> res = new HashMap<>(2);
+
+        res.put(PHYSICAL_READS, physicalReads());
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void resetStatistics() {
+        logicalReadCtr.reset();
+        physicalReadCtr.reset();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(IoStatisticsHolderCache.class, this,
+            "logicalReadCtr", logicalReadCtr,
+            "physicalReadCtr", physicalReadCtr,
+            "cacheName", cacheName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderIndex.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderIndex.java b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderIndex.java
new file mode 100644
index 0000000..8c09d67
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderIndex.java
@@ -0,0 +1,168 @@
+/*
+ * 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.stat;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.LongAdder;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Index statistics holder to gather statistics related to concrete index.
+ */
+public class IoStatisticsHolderIndex implements IoStatisticsHolder {
+    /** Display name of hash PK index. */
+    public static final String HASH_PK_IDX_NAME = "HASH_PK";
+
+    /** */
+    public static final String LOGICAL_READS_LEAF = "LOGICAL_READS_LEAF";
+
+    /** */
+    public static final String LOGICAL_READS_INNER = "LOGICAL_READS_INNER";
+
+    /** */
+    public static final String PHYSICAL_READS_LEAF = "PHYSICAL_READS_LEAF";
+
+    /** */
+    public static final String PHYSICAL_READS_INNER = "PHYSICAL_READS_INNER";
+
+    /** */
+    private LongAdder logicalReadLeafCtr = new LongAdder();
+
+    /** */
+    private LongAdder logicalReadInnerCtr = new LongAdder();
+
+    /** */
+    private LongAdder physicalReadLeafCtr = new LongAdder();
+
+    /** */
+    private LongAdder physicalReadInnerCtr = new LongAdder();
+
+    /** */
+    private final String cacheName;
+
+    /** */
+    private final String idxName;
+
+    /**
+     * @param cacheName Name of cache.
+     * @param idxName Name of index.
+     */
+    public IoStatisticsHolderIndex(String cacheName, String idxName) {
+        assert cacheName != null && idxName != null;
+
+        this.cacheName = cacheName;
+        this.idxName = idxName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void trackLogicalRead(long pageAddr) {
+        IndexPageType idxPageType = PageIO.deriveIndexPageType(pageAddr);
+
+        switch (idxPageType) {
+            case INNER:
+                logicalReadInnerCtr.increment();
+
+                IoStatisticsQueryHelper.trackLogicalReadQuery(pageAddr);
+
+                break;
+
+            case LEAF:
+                logicalReadLeafCtr.increment();
+
+                IoStatisticsQueryHelper.trackLogicalReadQuery(pageAddr);
+
+                break;
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    @Override public void trackPhysicalAndLogicalRead(long pageAddr) {
+        IndexPageType idxPageType = PageIO.deriveIndexPageType(pageAddr);
+
+        switch (idxPageType) {
+            case INNER:
+                logicalReadInnerCtr.increment();
+                physicalReadInnerCtr.increment();
+
+                IoStatisticsQueryHelper.trackPhysicalAndLogicalReadQuery(pageAddr);
+
+                break;
+
+            case LEAF:
+                logicalReadLeafCtr.increment();
+                physicalReadLeafCtr.increment();
+
+                IoStatisticsQueryHelper.trackPhysicalAndLogicalReadQuery(pageAddr);
+
+                break;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public long logicalReads() {
+        return logicalReadLeafCtr.longValue() + logicalReadInnerCtr.longValue();
+    }
+
+    /** {@inheritDoc} */
+    @Override public long physicalReads() {
+        return physicalReadLeafCtr.longValue() + physicalReadInnerCtr.longValue();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<String, Long> logicalReadsMap() {
+        Map<String, Long> res = new HashMap<>(3);
+
+        res.put(LOGICAL_READS_LEAF, logicalReadLeafCtr.longValue());
+        res.put(LOGICAL_READS_INNER, logicalReadInnerCtr.longValue());
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<String, Long> physicalReadsMap() {
+        Map<String, Long> res = new HashMap<>(3);
+
+        res.put(PHYSICAL_READS_LEAF, physicalReadLeafCtr.longValue());
+        res.put(PHYSICAL_READS_INNER, physicalReadInnerCtr.longValue());
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void resetStatistics() {
+        logicalReadLeafCtr.reset();
+        logicalReadInnerCtr.reset();
+        physicalReadLeafCtr.reset();
+        physicalReadInnerCtr.reset();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(IoStatisticsHolderIndex.class, this,
+            "logicalReadLeafCtr", logicalReadLeafCtr,
+            "logicalReadInnerCtr", logicalReadInnerCtr,
+            "physicalReadLeafCtr", physicalReadLeafCtr,
+            "physicalReadInnerCtr", physicalReadInnerCtr,
+            "cacheName", cacheName,
+            "idxName", idxName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderKey.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderKey.java b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderKey.java
new file mode 100644
index 0000000..86fff4f
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderKey.java
@@ -0,0 +1,91 @@
+/*
+ * 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.stat;
+
+import java.util.Objects;
+
+/**
+ * Statistics holder key
+ */
+public class IoStatisticsHolderKey {
+    /** Name. */
+    private final String name;
+
+    /** Second name. */
+    private final String subName;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     */
+    public IoStatisticsHolderKey(String name) {
+        this(name, null);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     * @param subName Second name.
+     */
+    public IoStatisticsHolderKey(String name, String subName) {
+        assert name != null;
+
+        this.name = name;
+        this.subName = subName;
+    }
+
+    /**
+     * @return Name.
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * @return Second name.
+     */
+    public String subName() {
+        return subName;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        IoStatisticsHolderKey other = (IoStatisticsHolderKey)o;
+
+        return Objects.equals(name, other.name) && Objects.equals(subName, other.subName);
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return 31 * (name != null ? name.hashCode() : 0) + (subName != null ? subName.hashCode() : 0);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return subName == null ? name : name + "." + subName;
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderNoOp.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderNoOp.java b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderNoOp.java
new file mode 100644
index 0000000..65f855b
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderNoOp.java
@@ -0,0 +1,70 @@
+/*
+ * 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.stat;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * No Operation IO statistics holder. Use in case statistics shouldn't be gathered.
+ */
+public class IoStatisticsHolderNoOp implements IoStatisticsHolder {
+    /** No-op statistics. */
+    public static final IoStatisticsHolderNoOp INSTANCE = new IoStatisticsHolderNoOp();
+
+    /**
+     * Private constructor.
+     */
+    private IoStatisticsHolderNoOp() {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void trackLogicalRead(long pageAddr) {
+    }
+
+    /** {@inheritDoc} */
+    @Override public void trackPhysicalAndLogicalRead(long pageAddr) {
+    }
+
+    /** {@inheritDoc} */
+    @Override public long logicalReads() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long physicalReads() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<String, Long> logicalReadsMap() {
+        return Collections.emptyMap();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<String, Long> physicalReadsMap() {
+        return Collections.emptyMap();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void resetStatistics() {
+        // No-op.
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderQuery.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderQuery.java b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderQuery.java
new file mode 100644
index 0000000..71684de
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/stat/IoStatisticsHolderQuery.java
@@ -0,0 +1,128 @@
+/*
+ * 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.stat;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.LongAdder;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Query Statistics holder to gather statistics related to concrete query.
+ * Used in {@code org.apache.ignite.internal.stat.IoStatisticsHolderIndex} and {@code org.apache.ignite.internal.stat.IoStatisticsHolderCache}.
+ * Query Statistics holder to gather statistics related to concrete query. Used in {@code
+ * org.apache.ignite.internal.stat.IoStatisticsHolderIndex} and {@code org.apache.ignite.internal.stat.IoStatisticsHolderCache}.
+ */
+public class IoStatisticsHolderQuery implements IoStatisticsHolder {
+    /** */
+    public static final String PHYSICAL_READS = "PHYSICAL_READS";
+
+    /** */
+    public static final String LOGICAL_READS = "LOGICAL_READS";
+
+    /** */
+    private LongAdder logicalReadCtr = new LongAdder();
+
+    /** */
+    private LongAdder physicalReadCtr = new LongAdder();
+
+    /** */
+    private final String qryId;
+
+    /**
+     * @param qryId Query id.
+     */
+    public IoStatisticsHolderQuery(String qryId) {
+        this.qryId = qryId;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void trackLogicalRead(long pageAddr) {
+        logicalReadCtr.increment();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void trackPhysicalAndLogicalRead(long pageAddr) {
+        logicalReadCtr.increment();
+
+        physicalReadCtr.increment();
+    }
+
+    /** {@inheritDoc} */
+    @Override public long logicalReads() {
+        return logicalReadCtr.longValue();
+    }
+
+    /** {@inheritDoc} */
+    @Override public long physicalReads() {
+        return physicalReadCtr.longValue();
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<String, Long> logicalReadsMap() {
+        Map<String, Long> res = new HashMap<>(2);
+
+        res.put(LOGICAL_READS, logicalReads());
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public Map<String, Long> physicalReadsMap() {
+        Map<String, Long> res = new HashMap<>(2);
+
+        res.put(PHYSICAL_READS, physicalReads());
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void resetStatistics() {
+        logicalReadCtr.reset();
+
+        physicalReadCtr.reset();
+    }
+
+    /**
+     * @return Query id.
+     */
+    public String queryId() {
+        return qryId;
+    }
+
+    /**
+     * Add given given statistics into this.
+     * Merge query statistics.
+     *
+     * @param logicalReads Logical reads which will be added to current query statistics.
+     * @param physicalReads Physical reads which will be added to current query statistics,
+     */
+    public void merge(long logicalReads, long physicalReads) {
+        logicalReadCtr.add(logicalReads);
+
+        physicalReadCtr.add(physicalReads);
+    }
+
+    @Override public String toString() {
+        return S.toString(IoStatisticsHolderQuery.class, this,
+            "logicalReadCtr", logicalReadCtr,
+            "physicalReadCtr", physicalReadCtr,
+            "qryId", qryId);
+    }
+}


[3/3] ignite git commit: IGNITE-9777: Implemented IO statistics for logic and physical page reads. This closes #4934.

Posted by vo...@apache.org.
IGNITE-9777: Implemented IO statistics for logic and physical page reads. This closes #4934.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/1eb57502
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/1eb57502
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/1eb57502

Branch: refs/heads/master
Commit: 1eb5750268cff544d214b804aabae493acf91546
Parents: 2a0f3df
Author: Yuriy Gerzhedovich <yg...@gridgain.com>
Authored: Fri Dec 14 12:40:56 2018 +0300
Committer: devozerov <pp...@gmail.com>
Committed: Fri Dec 14 12:40:56 2018 +0300

----------------------------------------------------------------------
 .../ignite/internal/GridKernalContext.java      |   6 +
 .../ignite/internal/GridKernalContextImpl.java  |  23 +-
 .../apache/ignite/internal/IgniteKernal.java    |   8 +
 .../ignite/internal/pagemem/PageSupport.java    |  14 +
 .../pagemem/impl/PageMemoryNoStoreImpl.java     |  19 +-
 .../processors/cache/CacheGroupContext.java     |  37 ++
 .../processors/cache/GridCacheContext.java      |   4 +-
 .../cache/IgniteCacheOffheapManagerImpl.java    | 146 +++---
 .../cache/persistence/CacheDataRowAdapter.java  |   7 +-
 .../cache/persistence/DataStructure.java        |  45 +-
 .../GridCacheDatabaseSharedManager.java         |  12 +-
 .../processors/cache/persistence/RowStore.java  |  24 +-
 .../persistence/freelist/AbstractFreeList.java  |  51 +-
 .../persistence/freelist/CacheFreeListImpl.java |   5 +-
 .../cache/persistence/freelist/FreeList.java    |  10 +-
 .../cache/persistence/freelist/PagesList.java   | 100 ++--
 .../metastorage/MetastorageRowStore.java        |   7 +-
 .../cache/persistence/pagemem/PageMemoryEx.java |   6 +-
 .../persistence/pagemem/PageMemoryImpl.java     |  24 +-
 .../cache/persistence/tree/BPlusTree.java       |  96 +++-
 .../cache/persistence/tree/io/PageIO.java       |  52 +-
 .../persistence/tree/reuse/ReuseListImpl.java   |   5 +-
 .../persistence/tree/util/PageHandler.java      |  43 +-
 .../wal/reader/StandaloneGridKernalContext.java |   5 +
 .../processors/cache/tree/CacheDataTree.java    |   6 +
 .../ignite/internal/stat/IndexPageType.java     |  33 ++
 .../internal/stat/IoStatisticsHolder.java       |  66 +++
 .../internal/stat/IoStatisticsHolderCache.java  | 121 +++++
 .../internal/stat/IoStatisticsHolderIndex.java  | 168 +++++++
 .../internal/stat/IoStatisticsHolderKey.java    |  91 ++++
 .../internal/stat/IoStatisticsHolderNoOp.java   |  70 +++
 .../internal/stat/IoStatisticsHolderQuery.java  | 128 +++++
 .../internal/stat/IoStatisticsManager.java      | 204 ++++++++
 .../IoStatisticsMetricsLocalMXBeanImpl.java     | 146 ++++++
 .../internal/stat/IoStatisticsQueryHelper.java  |  98 ++++
 .../ignite/internal/stat/IoStatisticsType.java  |  36 ++
 .../mxbean/IoStatisticsMetricsMXBean.java       | 172 +++++++
 ...IntegrityWithPrimaryIndexCorruptionTest.java |   5 +-
 .../database/CacheFreeListImplSelfTest.java     |  13 +-
 .../IoStatisticsCachePersistenceSelfTest.java   |  30 ++
 .../stat/IoStatisticsCacheSelfTest.java         | 262 ++++++++++
 .../stat/IoStatisticsManagerSelfTest.java       | 154 ++++++
 ...tatisticsMetricsLocalMXBeanImplSelfTest.java | 224 +++++++++
 .../processors/query/h2/database/H2Tree.java    |  15 +-
 .../query/h2/database/H2TreeIndex.java          |  29 +-
 .../index/IoStatisticsBasicIndexSelfTest.java   | 500 +++++++++++++++++++
 .../db/wal/IgniteWalRecoveryTest.java           |   6 +-
 .../util/GridCommandHandlerIndexingTest.java    |   3 +-
 48 files changed, 3084 insertions(+), 245 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
index e19450e..fdb8ebc 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContext.java
@@ -34,6 +34,7 @@ import org.apache.ignite.internal.managers.failover.GridFailoverManager;
 import org.apache.ignite.internal.managers.indexing.GridIndexingManager;
 import org.apache.ignite.internal.managers.loadbalancer.GridLoadBalancerManager;
 import org.apache.ignite.internal.processors.cache.mvcc.MvccProcessor;
+import org.apache.ignite.internal.stat.IoStatisticsManager;
 import org.apache.ignite.internal.processors.compress.CompressionProcessor;
 import org.apache.ignite.internal.worker.WorkersRegistry;
 import org.apache.ignite.internal.processors.affinity.GridAffinityProcessor;
@@ -696,6 +697,11 @@ public interface GridKernalContext extends Iterable<GridComponent> {
     public GridInternalSubscriptionProcessor internalSubscriptionProcessor();
 
     /**
+     * @return IO statistic manager.
+     */
+    public IoStatisticsManager ioStats();
+
+    /**
      * @return Default uncaught exception handler used by thread pools.
      */
     public Thread.UncaughtExceptionHandler uncaughtExceptionHandler();

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
index ef69167..4b4fbca 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/GridKernalContextImpl.java
@@ -38,29 +38,28 @@ import org.apache.ignite.IgniteSystemProperties;
 import org.apache.ignite.cluster.ClusterNode;
 import org.apache.ignite.configuration.IgniteConfiguration;
 import org.apache.ignite.failure.FailureType;
-import org.apache.ignite.internal.managers.encryption.GridEncryptionManager;
 import org.apache.ignite.internal.managers.checkpoint.GridCheckpointManager;
 import org.apache.ignite.internal.managers.collision.GridCollisionManager;
 import org.apache.ignite.internal.managers.communication.GridIoManager;
 import org.apache.ignite.internal.managers.deployment.GridDeploymentManager;
 import org.apache.ignite.internal.managers.discovery.GridDiscoveryManager;
+import org.apache.ignite.internal.managers.encryption.GridEncryptionManager;
 import org.apache.ignite.internal.managers.eventstorage.GridEventStorageManager;
 import org.apache.ignite.internal.managers.failover.GridFailoverManager;
 import org.apache.ignite.internal.managers.indexing.GridIndexingManager;
 import org.apache.ignite.internal.managers.loadbalancer.GridLoadBalancerManager;
-import org.apache.ignite.internal.processors.cache.mvcc.MvccProcessor;
-import org.apache.ignite.internal.processors.compress.CompressionProcessor;
-import org.apache.ignite.internal.worker.WorkersRegistry;
 import org.apache.ignite.internal.processors.affinity.GridAffinityProcessor;
 import org.apache.ignite.internal.processors.authentication.IgniteAuthenticationProcessor;
 import org.apache.ignite.internal.processors.cache.CacheConflictResolutionManager;
 import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
 import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
+import org.apache.ignite.internal.processors.cache.mvcc.MvccProcessor;
 import org.apache.ignite.internal.processors.cache.persistence.filename.PdsFoldersResolver;
 import org.apache.ignite.internal.processors.cacheobject.IgniteCacheObjectProcessor;
 import org.apache.ignite.internal.processors.closure.GridClosureProcessor;
 import org.apache.ignite.internal.processors.cluster.ClusterProcessor;
 import org.apache.ignite.internal.processors.cluster.GridClusterStateProcessor;
+import org.apache.ignite.internal.processors.compress.CompressionProcessor;
 import org.apache.ignite.internal.processors.continuous.GridContinuousProcessor;
 import org.apache.ignite.internal.processors.datastreamer.DataStreamProcessor;
 import org.apache.ignite.internal.processors.datastructures.DataStructuresProcessor;
@@ -90,6 +89,7 @@ import org.apache.ignite.internal.processors.session.GridTaskSessionProcessor;
 import org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
 import org.apache.ignite.internal.processors.task.GridTaskProcessor;
 import org.apache.ignite.internal.processors.timeout.GridTimeoutProcessor;
+import org.apache.ignite.internal.stat.IoStatisticsManager;
 import org.apache.ignite.internal.suggestions.GridPerformanceSuggestions;
 import org.apache.ignite.internal.util.IgniteExceptionRegistry;
 import org.apache.ignite.internal.util.StripedExecutor;
@@ -99,6 +99,7 @@ import org.apache.ignite.internal.util.tostring.GridToStringInclude;
 import org.apache.ignite.internal.util.typedef.X;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.worker.WorkersRegistry;
 import org.apache.ignite.lang.IgnitePredicate;
 import org.apache.ignite.plugin.PluginNotFoundException;
 import org.apache.ignite.plugin.PluginProvider;
@@ -293,7 +294,9 @@ public class GridKernalContextImpl implements GridKernalContext, Externalizable
     @GridToStringExclude
     private ClusterProcessor cluster;
 
-    /** */
+    /**
+     *
+     */
     @GridToStringExclude
     private CompressionProcessor compressProc;
 
@@ -423,6 +426,9 @@ public class GridKernalContextImpl implements GridKernalContext, Externalizable
     /** Recovery mode flag. Flag is set to {@code false} when discovery manager started. */
     private boolean recoveryMode = true;
 
+    /** IO statistics manager. */
+    private IoStatisticsManager ioStatMgr;
+
     /**
      * No-arg constructor is required by externalization.
      */
@@ -519,6 +525,8 @@ public class GridKernalContextImpl implements GridKernalContext, Externalizable
                 log.debug("Failed to load spring component, will not be able to extract userVersion from " +
                     "META-INF/ignite.xml.");
         }
+
+        ioStatMgr = new IoStatisticsManager();
     }
 
     /** {@inheritDoc} */
@@ -1150,6 +1158,11 @@ public class GridKernalContextImpl implements GridKernalContext, Externalizable
         return internalSubscriptionProc;
     }
 
+    /** {@inheritDoc} */
+    @Override public IoStatisticsManager ioStats() {
+        return ioStatMgr;
+    }
+
     /**
      * @param disconnected Disconnected flag.
      */

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
index a78037b..d1d0ce5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/IgniteKernal.java
@@ -167,6 +167,7 @@ import org.apache.ignite.internal.processors.session.GridTaskSessionProcessor;
 import org.apache.ignite.internal.processors.subscription.GridInternalSubscriptionProcessor;
 import org.apache.ignite.internal.processors.task.GridTaskProcessor;
 import org.apache.ignite.internal.processors.timeout.GridTimeoutProcessor;
+import org.apache.ignite.internal.stat.IoStatisticsMetricsLocalMXBeanImpl;
 import org.apache.ignite.internal.suggestions.GridPerformanceSuggestions;
 import org.apache.ignite.internal.suggestions.JvmConfigurationSuggestions;
 import org.apache.ignite.internal.suggestions.OsConfigurationSuggestions;
@@ -205,6 +206,7 @@ import org.apache.ignite.mxbean.ClusterMetricsMXBean;
 import org.apache.ignite.mxbean.DataStorageMXBean;
 import org.apache.ignite.mxbean.FailureHandlingMxBean;
 import org.apache.ignite.mxbean.IgniteMXBean;
+import org.apache.ignite.mxbean.IoStatisticsMetricsMXBean;
 import org.apache.ignite.mxbean.StripedExecutorMXBean;
 import org.apache.ignite.mxbean.ThreadPoolMXBean;
 import org.apache.ignite.mxbean.TransactionMetricsMxBean;
@@ -2449,6 +2451,8 @@ public class IgniteKernal implements IgniteEx, IgniteMXBean, Externalizable {
                 // Preserve interrupt status.
                 Thread.currentThread().interrupt();
             }
+
+            ctx.ioStats().stop();
         }
         else {
             // Proper notification.
@@ -4333,6 +4337,10 @@ public class IgniteKernal implements IgniteEx, IgniteMXBean, Externalizable {
             ClusterMetricsMXBean metricsBean = new ClusterMetricsMXBeanImpl(cluster());
             registerMBean("Kernal", metricsBean.getClass().getSimpleName(), metricsBean, ClusterMetricsMXBean.class);
 
+            //IO metrics
+            IoStatisticsMetricsMXBean ioStatMetricsBean = new IoStatisticsMetricsLocalMXBeanImpl(ctx.ioStats());
+            registerMBean("IOMetrics", ioStatMetricsBean.getClass().getSimpleName(), ioStatMetricsBean, IoStatisticsMetricsMXBean.class);
+
             // Transaction metrics
             TransactionMetricsMxBean txMetricsMXBean = new TransactionMetricsMxBeanImpl(ctx.cache().transactions().metrics());
             registerMBean("TransactionMetrics", txMetricsMXBean.getClass().getSimpleName(), txMetricsMXBean, TransactionMetricsMxBean.class);

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageSupport.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageSupport.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageSupport.java
index ed2311f..877234d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageSupport.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/PageSupport.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.pagemem;
 
 import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 
 /**
  * Supports operations on pages.
@@ -36,6 +37,19 @@ public interface PageSupport {
     public long acquirePage(int grpId, long pageId) throws IgniteCheckedException;
 
     /**
+     * Gets the page absolute pointer associated with the given page ID. Each page obtained with this method must be
+     * released by calling {@link #releasePage(int, long, long)}. This method will allocate page with given ID if it
+     * doesn't exist.
+     *
+     * @param grpId Cache group ID.
+     * @param pageId Page ID.
+     * @param statHolder Statistics holder to track IO operations.
+     * @return Page pointer.
+     * @throws IgniteCheckedException If failed.
+     */
+    public long acquirePage(int grpId, long pageId, IoStatisticsHolder statHolder) throws IgniteCheckedException;
+
+    /**
      *
      * @param grpId Cache group ID.
      * @param pageId Page ID to release.

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
index 193975e..cb22446 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/pagemem/impl/PageMemoryNoStoreImpl.java
@@ -38,6 +38,8 @@ import org.apache.ignite.internal.pagemem.PageMemory;
 import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.persistence.DataRegionMetricsImpl;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 import org.apache.ignite.internal.util.GridUnsafe;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.internal.util.OffheapReadWriteLock;
@@ -280,11 +282,9 @@ public class PageMemoryNoStoreImpl implements PageMemory {
                 relPtr = allocSeg.allocateFreePage(flags);
 
                 if (relPtr != INVALID_REL_PTR) {
-                    if (relPtr != INVALID_REL_PTR) {
-                        absPtr = allocSeg.absolute(PageIdUtils.pageIndex(relPtr));
+                    absPtr = allocSeg.absolute(PageIdUtils.pageIndex(relPtr));
 
-                        break;
-                    }
+                    break;
                 }
                 else
                     allocSeg = addSegment(seg0);
@@ -443,11 +443,20 @@ public class PageMemoryNoStoreImpl implements PageMemory {
 
     /** {@inheritDoc} */
     @Override public long acquirePage(int cacheId, long pageId) {
+        return acquirePage(cacheId, pageId, IoStatisticsHolderNoOp.INSTANCE);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long acquirePage(int cacheId, long pageId, IoStatisticsHolder statHolder) {
         int pageIdx = PageIdUtils.pageIndex(pageId);
 
         Segment seg = segment(pageIdx);
 
-        return seg.acquirePage(pageIdx);
+        long absPtr = seg.acquirePage(pageIdx);
+
+        statHolder.trackLogicalRead(absPtr + PAGE_OVERHEAD);
+
+        return absPtr;
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java
index fc4f79d..a482b5e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/CacheGroupContext.java
@@ -49,6 +49,9 @@ import org.apache.ignite.internal.processors.cache.persistence.freelist.FreeList
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
 import org.apache.ignite.internal.processors.cache.query.continuous.CounterSkipContext;
 import org.apache.ignite.internal.processors.query.QueryUtils;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
+import org.apache.ignite.internal.stat.IoStatisticsType;
 import org.apache.ignite.internal.util.typedef.CI1;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.CU;
@@ -66,6 +69,7 @@ import static org.apache.ignite.cache.CacheMode.REPLICATED;
 import static org.apache.ignite.cache.CacheRebalanceMode.NONE;
 import static org.apache.ignite.events.EventType.EVT_CACHE_REBALANCE_PART_UNLOADED;
 import static org.apache.ignite.internal.managers.communication.GridIoPolicy.AFFINITY_POOL;
+import static org.apache.ignite.internal.stat.IoStatisticsHolderIndex.HASH_PK_IDX_NAME;
 
 /**
  *
@@ -163,6 +167,13 @@ public class CacheGroupContext {
     /** Flag indicates that cache group is under recovering and not attached to topology. */
     private final AtomicBoolean recoveryMode;
 
+    /** Statistics holder to track IO operations for PK index pages. */
+    private final IoStatisticsHolder statHolderIdx;
+
+    /** Statistics holder to track IO operations for data pages. */
+    private final IoStatisticsHolder statHolderData;
+
+
     /**
      * @param ctx Context.
      * @param grpId Group ID.
@@ -225,6 +236,18 @@ public class CacheGroupContext {
         log = ctx.kernalContext().log(getClass());
 
         mxBean = new CacheGroupMetricsMXBeanImpl(this);
+
+        if (systemCache()) {
+            statHolderIdx = IoStatisticsHolderNoOp.INSTANCE;
+            statHolderData = IoStatisticsHolderNoOp.INSTANCE;
+        }
+        else {
+            statHolderIdx = ctx.kernalContext().ioStats().register(IoStatisticsType.HASH_INDEX,
+                cacheOrGroupName(), HASH_PK_IDX_NAME);
+
+            statHolderData = ctx.kernalContext().ioStats().register(IoStatisticsType.CACHE_GROUP,
+                cacheOrGroupName());
+        }
     }
 
     /**
@@ -1147,4 +1170,18 @@ public class CacheGroupContext {
     private void persistLocalWalState(boolean enabled) {
         shared().database().walEnabled(grpId, enabled, true);
     }
+
+    /**
+     * @return Statistics holder to track cache IO operations.
+     */
+    public IoStatisticsHolder statisticsHolderIdx() {
+        return statHolderIdx;
+    }
+
+    /**
+     * @return Statistics holder to track cache IO operations.
+     */
+    public IoStatisticsHolder statisticsHolderData() {
+        return statHolderData;
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheContext.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheContext.java
index 50db168..9d282aa 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheContext.java
@@ -66,11 +66,11 @@ import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.datastructures.CacheDataStructuresManager;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtCacheAdapter;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtCacheEntry;
-import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtLocalPartition;
-import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionTopology;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTopologyFuture;
 import org.apache.ignite.internal.processors.cache.distributed.dht.GridDhtTransactionalCacheAdapter;
 import org.apache.ignite.internal.processors.cache.distributed.dht.colocated.GridDhtColocatedCache;
+import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtLocalPartition;
+import org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionTopology;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearCacheAdapter;
 import org.apache.ignite.internal.processors.cache.distributed.near.GridNearTransactionalCache;
 import org.apache.ignite.internal.processors.cache.dr.GridCacheDrManager;

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
index 1113597..5997f57 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheOffheapManagerImpl.java
@@ -89,6 +89,7 @@ import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccSnapshot
 import org.apache.ignite.internal.processors.cache.tree.mvcc.search.MvccTreeClosure;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
 import org.apache.ignite.internal.processors.query.GridQueryRowCacheCleaner;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 import org.apache.ignite.internal.util.GridAtomicLong;
 import org.apache.ignite.internal.util.GridCloseableIteratorAdapter;
 import org.apache.ignite.internal.util.GridEmptyCloseableIterator;
@@ -620,7 +621,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
         if (entry.detached() || entry.isNear())
             return;
 
-        dataStore(entry.localPartition()).mvccRemoveAll(entry.context(), entry.key());
+            dataStore(entry.localPartition()).mvccRemoveAll(entry.context(), entry.key());
     }
 
     /** {@inheritDoc} */
@@ -1092,6 +1093,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
     /**
      * @param item Item.
      * @return Single item iterator.
+     * @param <T> Type of item.
      */
     private <T> Iterator<T> singletonIterator(final T item) {
         return new Iterator<T>() {
@@ -1238,6 +1240,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
      * @param partCntrs Partition counters map.
      * @param missing Set of partitions need to populate if partition is missing or failed to reserve.
      * @return Historical iterator.
+     * @throws IgniteCheckedException If failed.
      */
     @Nullable protected IgniteHistoricalIterator historicalIterator(CachePartitionPartialCountersMap partCntrs, Set<Integer> missing)
         throws IgniteCheckedException {
@@ -1704,7 +1707,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
 
             DataRow dataRow = makeDataRow(key, val, ver, expireTime, cacheId);
 
-            if (canUpdateOldRow(cctx, oldRow, dataRow) && rowStore.updateRow(oldRow.link(), dataRow))
+            if (canUpdateOldRow(cctx, oldRow, dataRow) && rowStore.updateRow(oldRow.link(), dataRow, grp.statisticsHolderData()))
                 dataRow.link(oldRow.link());
             else {
                 CacheObjectContext coCtx = cctx.cacheObjectContext();
@@ -1712,7 +1715,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                 key.valueBytes(coCtx);
                 val.valueBytes(coCtx);
 
-                rowStore.addRow(dataRow);
+                rowStore.addRow(dataRow, grp.statisticsHolderData());
             }
 
             assert dataRow.link() != 0 : dataRow;
@@ -1787,12 +1790,12 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                     if (!grp.storeCacheIdInDataPage() && updateRow.cacheId() != CU.UNDEFINED_CACHE_ID) {
                         updateRow.cacheId(CU.UNDEFINED_CACHE_ID);
 
-                        rowStore.addRow(updateRow);
+                        rowStore.addRow(updateRow, grp.statisticsHolderData());
 
                         updateRow.cacheId(cctx.cacheId());
                     }
                     else
-                        rowStore.addRow(updateRow);
+                        rowStore.addRow(updateRow, grp.statisticsHolderData());
 
                     dataTree.putx(updateRow);
 
@@ -2007,7 +2010,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
 
                     // Mark old version as removed.
                     if (res == ResultType.PREV_NOT_NULL) {
-                        rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot);
+                        rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot, grp.statisticsHolderData());
 
                         if (op == CacheInvokeEntry.Operation.REMOVE) {
                             updateRow.resultType(ResultType.REMOVED_NOT_NULL);
@@ -2023,17 +2026,17 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                         assert op != CacheInvokeEntry.Operation.REMOVE;
                 }
                 else if (oldRow != null)
-                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot);
+                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot, grp.statisticsHolderData());
 
                 if (!grp.storeCacheIdInDataPage() && updateRow.cacheId() != CU.UNDEFINED_CACHE_ID) {
                     updateRow.cacheId(CU.UNDEFINED_CACHE_ID);
 
-                    rowStore.addRow(updateRow);
+                    rowStore.addRow(updateRow, grp.statisticsHolderData());
 
                     updateRow.cacheId(cctx.cacheId());
                 }
                 else
-                    rowStore.addRow(updateRow);
+                    rowStore.addRow(updateRow, grp.statisticsHolderData());
 
                 if (needHistory) {
                     assert updateRow.link() != 0;
@@ -2177,7 +2180,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
 
                     assert oldRow != null && oldRow.link() != 0 : oldRow;
 
-                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot);
+                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot, grp.statisticsHolderData());
 
                     clearPendingEntries(cctx, oldRow);
                 }
@@ -2300,7 +2303,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
 
                     oldRow.key(key);
 
-                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot);
+                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot, grp.statisticsHolderData());
                 }
                 else
                     assert res == ResultType.PREV_NULL;
@@ -2308,12 +2311,12 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                 if (!grp.storeCacheIdInDataPage() && updateRow.cacheId() != CU.UNDEFINED_CACHE_ID) {
                     updateRow.cacheId(CU.UNDEFINED_CACHE_ID);
 
-                    rowStore.addRow(updateRow);
+                    rowStore.addRow(updateRow, grp.statisticsHolderData());
 
                     updateRow.cacheId(cctx.cacheId());
                 }
                 else
-                    rowStore.addRow(updateRow);
+                    rowStore.addRow(updateRow, grp.statisticsHolderData());
 
                 boolean old = dataTree.putx(updateRow);
 
@@ -2385,7 +2388,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
 
                     assert oldRow != null && oldRow.link() != 0 : oldRow;
 
-                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot);
+                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot, grp.statisticsHolderData());
 
                     clearPendingEntries(cctx, oldRow);
                 }
@@ -2436,7 +2439,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                         clearPendingEntries(cctx, row);
                 }
 
-                rowStore.removeRow(row.link());
+                rowStore.removeRow(row.link(), grp.statisticsHolderData());
 
                 if (first)
                     first = false;
@@ -2472,7 +2475,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
 
                         clearPendingEntries(cctx, oldRow);
 
-                        rowStore.removeRow(cleanupRow.link());
+                        rowStore.removeRow(cleanupRow.link(), grp.statisticsHolderData());
 
                         res++;
                     }
@@ -2492,7 +2495,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
             CacheDataRow row0 = dataTree.findOne(row, CacheDataRowAdapter.RowData.LINK_ONLY);
 
             if (row0 != null)
-                rowStore.updateDataRow(row0.link(), mvccUpdateTxStateHint, null);
+                rowStore.updateDataRow(row0.link(), mvccUpdateTxStateHint, null, grp.statisticsHolderData());
         }
 
         /** {@inheritDoc} */
@@ -2525,13 +2528,13 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
 
                 assert cctx.shared().database().checkpointLockIsHeldByThread();
 
-                if (canUpdateOldRow(cctx, oldRow, dataRow) && rowStore.updateRow(oldRow.link(), dataRow)) {
+                if (canUpdateOldRow(cctx, oldRow, dataRow) && rowStore.updateRow(oldRow.link(), dataRow, grp.statisticsHolderData())) {
                     old = oldRow;
 
                     dataRow.link(oldRow.link());
                 }
                 else {
-                    rowStore.addRow(dataRow);
+                    rowStore.addRow(dataRow, grp.statisticsHolderData());
 
                     assert dataRow.link() != 0 : dataRow;
 
@@ -2609,19 +2612,19 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
 
                     assert oldRow != null && oldRow.link() != 0 : oldRow;
 
-                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot);
+                    rowStore.updateDataRow(oldRow.link(), mvccUpdateMarker, mvccSnapshot, grp.statisticsHolderData());
                 }
 
                 if (val != null) {
                     if (!grp.storeCacheIdInDataPage() && updateRow.cacheId() != CU.UNDEFINED_CACHE_ID) {
                         updateRow.cacheId(CU.UNDEFINED_CACHE_ID);
 
-                        rowStore.addRow(updateRow);
+                        rowStore.addRow(updateRow, grp.statisticsHolderData());
 
                         updateRow.cacheId(cctx.cacheId());
                     }
                     else
-                        rowStore.addRow(updateRow);
+                        rowStore.addRow(updateRow, grp.statisticsHolderData());
 
                     boolean old = dataTree.putx(updateRow);
 
@@ -2664,7 +2667,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                 assert oldRow.link() != 0 : oldRow;
 
                 if (newRow.link() != oldRow.link())
-                    rowStore.removeRow(oldRow.link());
+                    rowStore.removeRow(oldRow.link(), grp.statisticsHolderData());
             }
 
             updateIgfsMetrics(cctx, key, (oldRow != null ? oldRow.value() : null), newRow.value());
@@ -2735,7 +2738,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                 qryMgr.remove(key, oldRow);
 
             if (oldRow != null)
-                rowStore.removeRow(oldRow.link());
+                rowStore.removeRow(oldRow.link(), grp.statisticsHolderData());
 
             updateIgfsMetrics(cctx, key, (oldRow != null ? oldRow.value() : null), null);
         }
@@ -2743,7 +2746,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
         /**
          * @param cctx Cache context.
          * @param oldRow Old row.
-         * @throws IgniteCheckedException
+         * @throws IgniteCheckedException If failed.
          */
         private void clearPendingEntries(GridCacheContext cctx, CacheDataRow oldRow)
             throws IgniteCheckedException {
@@ -2830,7 +2833,12 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
             throws IgniteCheckedException {
             int cacheId = cctx.cacheId();
 
-            return dataTree.find(new MvccMaxSearchRow(cacheId, key), new MvccMinSearchRow(cacheId, key), x);
+            GridCursor<CacheDataRow> cursor = dataTree.find(
+                new MvccMaxSearchRow(cacheId, key),
+                new MvccMinSearchRow(cacheId, key),
+                x);
+
+            return cursor;
         }
 
         /** {@inheritDoc} */
@@ -2883,14 +2891,17 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
         @Override public GridCursor<? extends CacheDataRow> cursor(MvccSnapshot mvccSnapshot)
             throws IgniteCheckedException {
 
+            GridCursor<? extends CacheDataRow> cursor;
             if (mvccSnapshot != null) {
                 assert grp.mvccEnabled();
 
-                return dataTree.find(null, null,
+                cursor = dataTree.find(null, null,
                     new MvccFirstVisibleRowTreeClosure(grp.singleCacheContext(), mvccSnapshot), null);
             }
+            else
+                cursor = dataTree.find(null, null);
 
-            return dataTree.find(null, null);
+            return cursor;
         }
 
         /** {@inheritDoc} */
@@ -2933,16 +2944,19 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                 upperRow = upper != null ? new SearchRow(CU.UNDEFINED_CACHE_ID, upper) : null;
             }
 
+            GridCursor<? extends CacheDataRow> cursor;
+
             if (snapshot != null) {
                 assert grp.mvccEnabled();
 
-                GridCacheContext cctx =
-                    grp.sharedGroup() ? grp.shared().cacheContext(cacheId) : grp.singleCacheContext();
+                GridCacheContext cctx = grp.sharedGroup() ? grp.shared().cacheContext(cacheId) : grp.singleCacheContext();
 
-                return dataTree.find(lowerRow, upperRow, new MvccFirstVisibleRowTreeClosure(cctx, snapshot), x);
+                cursor = dataTree.find(lowerRow, upperRow, new MvccFirstVisibleRowTreeClosure(cctx, snapshot), x);
             }
+            else
+                cursor = dataTree.find(lowerRow, upperRow, x);
 
-            return dataTree.find(lowerRow, upperRow, x);
+            return cursor;
         }
 
         /** {@inheritDoc} */
@@ -2952,7 +2966,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
             dataTree.destroy(new IgniteInClosure<CacheSearchRow>() {
                 @Override public void apply(CacheSearchRow row) {
                     try {
-                        rowStore.removeRow(row.link());
+                        rowStore.removeRow(row.link(), grp.statisticsHolderData());
                     }
                     catch (IgniteCheckedException e) {
                         U.error(log, "Failed to remove row [link=" + row.link() + "]");
@@ -2993,7 +3007,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
 
                     assert res : row;
 
-                    rowStore.removeRow(row.link());
+                    rowStore.removeRow(row.link(), grp.statisticsHolderData());
 
                     decrementSize(cacheId);
                 }
@@ -3056,9 +3070,8 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
             CacheObject newVal
         ) {
             GridCacheAdapter cache = cctx.cache();
-            if (cache == null) {
+            if (cache == null)
                 return;
-            }
 
             // In case we deal with IGFS cache, count updated data
             if (cache.isIgfsDataCache() &&
@@ -3173,7 +3186,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                     if (!grp.storeCacheIdInDataPage() && cacheId != CU.UNDEFINED_CACHE_ID)
                         cacheId(CU.UNDEFINED_CACHE_ID);
 
-                    rowStore().addRow(this);
+                    rowStore().addRow(this, grp.statisticsHolderData());
 
                     cacheId(cacheId);
                 }
@@ -3251,7 +3264,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                     if (!grp.storeCacheIdInDataPage() && cacheId != CU.UNDEFINED_CACHE_ID)
                         cacheId(CU.UNDEFINED_CACHE_ID);
 
-                    rowStore().addRow(this);
+                    rowStore().addRow(this, grp.statisticsHolderData());
 
                     cacheId(cacheId);
                 }
@@ -3264,7 +3277,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                         oldRow.newMvccOperationCounter() != newMvccOperationCounter() ||
                         oldRow.newMvccTxState() != newMvccTxState()) {
 
-                        rowStore().updateDataRow(oldRow.link(), mvccApplyChanges, this);
+                        rowStore().updateDataRow(oldRow.link(), mvccApplyChanges, this, grp.statisticsHolderData());
                     }
                 }
             }
@@ -3287,21 +3300,21 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
     private final class MvccMarkUpdatedHandler extends PageHandler<MvccVersion, Boolean> {
         /** {@inheritDoc} */
         @Override public Boolean run(int cacheId, long pageId, long page, long pageAddr, PageIO io, Boolean walPlc,
-            MvccVersion newVer, int itemId) throws IgniteCheckedException {
+            MvccVersion newVer, int itemId, IoStatisticsHolder statHolder) throws IgniteCheckedException {
             assert grp.mvccEnabled();
 
             DataPageIO iox = (DataPageIO)io;
 
-            int offset = iox.getPayloadOffset(pageAddr, itemId,
+            int off = iox.getPayloadOffset(pageAddr, itemId,
                 grp.dataRegion().pageMemory().realPageSize(grp.groupId()), MVCC_INFO_SIZE);
 
-            long newCrd = iox.newMvccCoordinator(pageAddr, offset);
-            long newCntr = iox.newMvccCounter(pageAddr, offset);
-            int newOpCntr = iox.newMvccOperationCounter(pageAddr, offset);
+            long newCrd = iox.newMvccCoordinator(pageAddr, off);
+            long newCntr = iox.newMvccCounter(pageAddr, off);
+            int newOpCntr = iox.newMvccOperationCounter(pageAddr, off);
 
             assert newCrd == MVCC_CRD_COUNTER_NA || state(grp, newCrd, newCntr, newOpCntr) == TxState.ABORTED;
 
-            iox.updateNewVersion(pageAddr, offset, newVer, TxState.NA);
+            iox.updateNewVersion(pageAddr, off, newVer, TxState.NA);
 
             if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
                 ctx.wal().log(new DataPageMvccMarkUpdatedRecord(cacheId, pageId, itemId,
@@ -3317,24 +3330,23 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
     private final class MvccUpdateTxStateHintHandler extends PageHandler<Void, Boolean> {
         /** {@inheritDoc} */
         @Override public Boolean run(int cacheId, long pageId, long page, long pageAddr, PageIO io,
-            Boolean walPlc, Void ignore,
-            int itemId) throws IgniteCheckedException {
+            Boolean walPlc, Void ignore, int itemId, IoStatisticsHolder statHolder) throws IgniteCheckedException {
 
             DataPageIO iox = (DataPageIO)io;
 
-            int offset = iox.getPayloadOffset(pageAddr, itemId,
+            int off = iox.getPayloadOffset(pageAddr, itemId,
                 grp.dataRegion().pageMemory().realPageSize(grp.groupId()), MVCC_INFO_SIZE);
 
-            long crd = iox.mvccCoordinator(pageAddr, offset);
-            long cntr = iox.mvccCounter(pageAddr, offset);
-            int opCntr = iox.mvccOperationCounter(pageAddr, offset);
+            long crd = iox.mvccCoordinator(pageAddr, off);
+            long cntr = iox.mvccCounter(pageAddr, off);
+            int opCntr = iox.mvccOperationCounter(pageAddr, off);
             byte txState = (byte)(opCntr >>> MVCC_HINTS_BIT_OFF);
 
             if (txState == TxState.NA) {
                 byte state = state(grp, crd, cntr, opCntr);
 
                 if (state == TxState.COMMITTED || state == TxState.ABORTED) {
-                    iox.mvccOperationCounter(pageAddr, offset, opCntr | (state << MVCC_HINTS_BIT_OFF));
+                    iox.mvccOperationCounter(pageAddr, off, opCntr | (state << MVCC_HINTS_BIT_OFF));
 
                     if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
                         ctx.wal().log(new DataPageMvccUpdateTxStateHintRecord(cacheId, pageId, itemId, state));
@@ -3343,16 +3355,16 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                     throw unexpectedStateException(grp, state, crd, cntr, opCntr);
             }
 
-            long newCrd = iox.newMvccCoordinator(pageAddr, offset);
-            long newCntr = iox.newMvccCounter(pageAddr, offset);
-            int newOpCntr = iox.newMvccOperationCounter(pageAddr, offset);
+            long newCrd = iox.newMvccCoordinator(pageAddr, off);
+            long newCntr = iox.newMvccCounter(pageAddr, off);
+            int newOpCntr = iox.newMvccOperationCounter(pageAddr, off);
             byte newTxState = (byte)(newOpCntr >>> MVCC_HINTS_BIT_OFF);
 
             if (newCrd != MVCC_CRD_COUNTER_NA && newTxState == TxState.NA) {
                 byte state = state(grp, newCrd, newCntr, newOpCntr);
 
                 if (state == TxState.COMMITTED || state == TxState.ABORTED) {
-                    iox.newMvccOperationCounter(pageAddr, offset, newOpCntr | (state << MVCC_HINTS_BIT_OFF));
+                    iox.newMvccOperationCounter(pageAddr, off, newOpCntr | (state << MVCC_HINTS_BIT_OFF));
 
                     if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
                         ctx.wal().log(new DataPageMvccUpdateNewTxStateHintRecord(cacheId, pageId, itemId, state));
@@ -3371,23 +3383,23 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
     private final class MvccApplyChangesHandler extends PageHandler<MvccDataRow, Boolean> {
         /** {@inheritDoc} */
         @Override public Boolean run(int cacheId, long pageId, long page, long pageAddr, PageIO io, Boolean walPlc,
-            MvccDataRow newRow, int itemId) throws IgniteCheckedException {
+            MvccDataRow newRow, int itemId, IoStatisticsHolder statHolder) throws IgniteCheckedException {
             assert grp.mvccEnabled();
 
             DataPageIO iox = (DataPageIO)io;
 
-            int offset = iox.getPayloadOffset(pageAddr, itemId,
+            int off = iox.getPayloadOffset(pageAddr, itemId,
                 grp.dataRegion().pageMemory().realPageSize(grp.groupId()), MVCC_INFO_SIZE);
 
-            long crd = iox.mvccCoordinator(pageAddr, offset);
-            long cntr = iox.mvccCounter(pageAddr, offset);
-            int opCntrAndHint = iox.mvccOperationCounter(pageAddr, offset);
+            long crd = iox.mvccCoordinator(pageAddr, off);
+            long cntr = iox.mvccCounter(pageAddr, off);
+            int opCntrAndHint = iox.mvccOperationCounter(pageAddr, off);
             int opCntr = opCntrAndHint & ~MVCC_OP_COUNTER_MASK;
             byte txState = (byte)(opCntrAndHint >>> MVCC_HINTS_BIT_OFF);
 
-            long newCrd = iox.newMvccCoordinator(pageAddr, offset);
-            long newCntr = iox.newMvccCounter(pageAddr, offset);
-            int newOpCntrAndHint = iox.newMvccOperationCounter(pageAddr, offset);
+            long newCrd = iox.newMvccCoordinator(pageAddr, off);
+            long newCntr = iox.newMvccCounter(pageAddr, off);
+            int newOpCntrAndHint = iox.newMvccOperationCounter(pageAddr, off);
             int newOpCntr = newOpCntrAndHint & ~MVCC_OP_COUNTER_MASK;
             byte newTxState = (byte)(newOpCntrAndHint >>> MVCC_HINTS_BIT_OFF);
 
@@ -3398,7 +3410,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
             if (txState != newRow.mvccTxState() && newRow.mvccTxState() != TxState.NA) {
                 assert txState == TxState.NA;
 
-                iox.mvccOperationCounter(pageAddr, offset, opCntr | (newRow.mvccTxState() << MVCC_HINTS_BIT_OFF));
+                iox.mvccOperationCounter(pageAddr, off, opCntr | (newRow.mvccTxState() << MVCC_HINTS_BIT_OFF));
 
                 if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
                     ctx.wal().log(new DataPageMvccUpdateTxStateHintRecord(cacheId, pageId, itemId, newRow.mvccTxState()));
@@ -3411,7 +3423,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
                 newRow.newMvccCounter(),
                 newRow.newMvccOperationCounter()) != 0) {
 
-                iox.updateNewVersion(pageAddr, offset, newRow.newMvccVersion(), newRow.newMvccTxState());
+                iox.updateNewVersion(pageAddr, off, newRow.newMvccVersion(), newRow.newMvccTxState());
 
                 if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
                     ctx.wal().log(new DataPageMvccMarkUpdatedRecord(cacheId, pageId, itemId,
@@ -3420,7 +3432,7 @@ public class IgniteCacheOffheapManagerImpl implements IgniteCacheOffheapManager
             else if (newTxState != newRow.newMvccTxState() && newRow.newMvccTxState() != TxState.NA) {
                 assert newTxState == TxState.NA;
 
-                iox.newMvccOperationCounter(pageAddr, offset, newOpCntr | (newRow.newMvccTxState() << MVCC_HINTS_BIT_OFF));
+                iox.newMvccOperationCounter(pageAddr, off, newOpCntr | (newRow.newMvccTxState() << MVCC_HINTS_BIT_OFF));
 
                 if (isWalDeltaRecordNeeded(grp.dataRegion().pageMemory(), cacheId, pageId, page, ctx.wal(), walPlc))
                     ctx.wal().log(new DataPageMvccUpdateNewTxStateHintRecord(cacheId, pageId, itemId, newRow.newMvccTxState()));

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CacheDataRowAdapter.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CacheDataRowAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CacheDataRowAdapter.java
index e6360df..85be9d2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CacheDataRowAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/CacheDataRowAdapter.java
@@ -35,6 +35,8 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.CacheVers
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPagePayload;
 import org.apache.ignite.internal.processors.cache.version.GridCacheVersion;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 import org.apache.ignite.internal.util.tostring.GridToStringExclude;
 import org.apache.ignite.internal.util.tostring.GridToStringInclude;
 import org.apache.ignite.internal.util.typedef.internal.S;
@@ -143,7 +145,10 @@ public class CacheDataRowAdapter implements CacheDataRow {
 
             int grpId = grp != null ? grp.groupId() : 0;
 
-            final long page = pageMem.acquirePage(grpId, pageId);
+            final IoStatisticsHolder statHolder = (grp != null) ?
+                grp.statisticsHolderData() : IoStatisticsHolderNoOp.INSTANCE;
+
+            final long page = pageMem.acquirePage(grpId, pageId, statHolder);
 
             try {
                 long pageAddr = pageMem.readLock(grpId, pageId, page); // Non-empty data page must not be recycled.

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
index c23a970..dbf2703 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/DataStructure.java
@@ -31,6 +31,8 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseB
 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.PageLockListener;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 import org.apache.ignite.internal.util.typedef.internal.U;
 
 import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_DATA;
@@ -133,15 +135,16 @@ public abstract class DataStructure implements PageLockListener {
 
     /**
      * @param pageId Page ID.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Page absolute pointer.
      * @throws IgniteCheckedException If failed.
      */
-    protected final long acquirePage(long pageId) throws IgniteCheckedException {
+    protected final long acquirePage(long pageId, IoStatisticsHolder statHolder) throws IgniteCheckedException {
         assert PageIdUtils.flag(pageId) == FLAG_IDX && PageIdUtils.partId(pageId) == INDEX_PARTITION ||
             PageIdUtils.flag(pageId) == FLAG_DATA && PageIdUtils.partId(pageId) <= MAX_PARTITION_ID :
             U.hexLong(pageId) + " flag=" + PageIdUtils.flag(pageId) + " part=" + PageIdUtils.partId(pageId);
 
-        return pageMem.acquirePage(grpId, pageId);
+        return pageMem.acquirePage(grpId, pageId, statHolder);
     }
 
     /**
@@ -227,6 +230,7 @@ public abstract class DataStructure implements PageLockListener {
      * @param h Handler.
      * @param intArg Argument of type {@code int}.
      * @param lockFailed Result in case of lock failure due to page recycling.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Handler result.
      * @throws IgniteCheckedException If failed.
      */
@@ -234,8 +238,9 @@ public abstract class DataStructure implements PageLockListener {
         long pageId,
         PageHandler<?, R> h,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.writePage(pageMem, grpId, pageId, this, h, null, null, null, null, intArg, lockFailed);
+        R lockFailed,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
+        return PageHandler.writePage(pageMem, grpId, pageId, this, h, null, null, null, null, intArg, lockFailed, statHolder);
     }
 
     /**
@@ -244,6 +249,7 @@ public abstract class DataStructure implements PageLockListener {
      * @param arg Argument.
      * @param intArg Argument of type {@code int}.
      * @param lockFailed Result in case of lock failure due to page recycling.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Handler result.
      * @throws IgniteCheckedException If failed.
      */
@@ -252,8 +258,9 @@ public abstract class DataStructure implements PageLockListener {
         PageHandler<X, R> h,
         X arg,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.writePage(pageMem, grpId, pageId, this, h, null, null, null, arg, intArg, lockFailed);
+        R lockFailed,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
+        return PageHandler.writePage(pageMem, grpId, pageId, this, h, null, null, null, arg, intArg, lockFailed, statHolder);
     }
 
     /**
@@ -263,6 +270,7 @@ public abstract class DataStructure implements PageLockListener {
      * @param arg Argument.
      * @param intArg Argument of type {@code int}.
      * @param lockFailed Result in case of lock failure due to page recycling.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Handler result.
      * @throws IgniteCheckedException If failed.
      */
@@ -272,8 +280,9 @@ public abstract class DataStructure implements PageLockListener {
         PageHandler<X, R> h,
         X arg,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.writePage(pageMem, grpId, pageId, page, this, h, null, null, null, arg, intArg, lockFailed);
+        R lockFailed,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
+        return PageHandler.writePage(pageMem, grpId, pageId, page, this, h, null, null, null, arg, intArg, lockFailed, statHolder);
     }
 
     /**
@@ -283,6 +292,7 @@ public abstract class DataStructure implements PageLockListener {
      * @param arg Argument.
      * @param intArg Argument of type {@code int}.
      * @param lockFailed Result in case of lock failure due to page recycling.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Handler result.
      * @throws IgniteCheckedException If failed.
      */
@@ -292,8 +302,9 @@ public abstract class DataStructure implements PageLockListener {
         PageIO init,
         X arg,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.writePage(pageMem, grpId, pageId, this, h, init, wal, null, arg, intArg, lockFailed);
+        R lockFailed,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
+        return PageHandler.writePage(pageMem, grpId, pageId, this, h, init, wal, null, arg, intArg, lockFailed, statHolder);
     }
 
     /**
@@ -302,6 +313,7 @@ public abstract class DataStructure implements PageLockListener {
      * @param arg Argument.
      * @param intArg Argument of type {@code int}.
      * @param lockFailed Result in case of lock failure due to page recycling.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Handler result.
      * @throws IgniteCheckedException If failed.
      */
@@ -310,8 +322,9 @@ public abstract class DataStructure implements PageLockListener {
         PageHandler<X, R> h,
         X arg,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.readPage(pageMem, grpId, pageId, this, h, arg, intArg, lockFailed);
+        R lockFailed,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
+        return PageHandler.readPage(pageMem, grpId, pageId, this, h, arg, intArg, lockFailed, statHolder);
     }
 
     /**
@@ -321,6 +334,7 @@ public abstract class DataStructure implements PageLockListener {
      * @param arg Argument.
      * @param intArg Argument of type {@code int}.
      * @param lockFailed Result in case of lock failure due to page recycling.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Handler result.
      * @throws IgniteCheckedException If failed.
      */
@@ -330,8 +344,9 @@ public abstract class DataStructure implements PageLockListener {
         PageHandler<X, R> h,
         X arg,
         int intArg,
-        R lockFailed) throws IgniteCheckedException {
-        return PageHandler.readPage(pageMem, grpId, pageId, page, this, h, arg, intArg, lockFailed);
+        R lockFailed,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
+        return PageHandler.readPage(pageMem, grpId, pageId, page, this, h, arg, intArg, lockFailed, statHolder);
     }
 
     /**
@@ -340,7 +355,7 @@ public abstract class DataStructure implements PageLockListener {
      * @throws IgniteCheckedException if failed.
      */
     protected final void init(long pageId, PageIO init) throws IgniteCheckedException {
-        PageHandler.initPage(pageMem, grpId, pageId, init, wal, this);
+        PageHandler.initPage(pageMem, grpId, pageId, init, wal, this, IoStatisticsHolderNoOp.INSTANCE);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheDatabaseSharedManager.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheDatabaseSharedManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheDatabaseSharedManager.java
index 73c5fbb..11c6091 100755
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheDatabaseSharedManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/GridCacheDatabaseSharedManager.java
@@ -139,6 +139,7 @@ import org.apache.ignite.internal.processors.cache.persistence.wal.FileWALPointe
 import org.apache.ignite.internal.processors.cache.persistence.wal.crc.IgniteDataIntegrityViolationException;
 import org.apache.ignite.internal.processors.port.GridPortRecord;
 import org.apache.ignite.internal.processors.query.GridQueryProcessor;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 import org.apache.ignite.internal.util.GridMultiCollectionWrapper;
 import org.apache.ignite.internal.util.IgniteUtils;
 import org.apache.ignite.internal.util.future.CountDownFuture;
@@ -2117,7 +2118,7 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
                             if (pageMem == null)
                                 break;
 
-                            long page = pageMem.acquirePage(grpId, pageId, true);
+                                long page = pageMem.acquirePage(grpId, pageId, IoStatisticsHolderNoOp.INSTANCE, true);
 
                             try {
                                 long pageAddr = pageMem.writeLock(grpId, pageId, page);
@@ -2187,9 +2188,9 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
                             if (pageMem == null)
                                 break;
 
-                            // Here we do not require tag check because we may be applying memory changes after
-                            // several repetitive restarts and the same pages may have changed several times.
-                            long page = pageMem.acquirePage(grpId, pageId, true);
+                                // Here we do not require tag check because we may be applying memory changes after
+                                // several repetitive restarts and the same pages may have changed several times.
+                                long page = pageMem.acquirePage(grpId, pageId, IoStatisticsHolderNoOp.INSTANCE, true);
 
                             try {
                                 long pageAddr = pageMem.writeLock(grpId, pageId, page);
@@ -2454,7 +2455,8 @@ public class GridCacheDatabaseSharedManager extends IgniteCacheDatabaseSharedMan
                         if (pageMem == null)
                             break;
 
-                        long page = pageMem.acquirePage(rec0.groupId(), rec0.pageId(), true);
+                        long page = pageMem.acquirePage(
+                            rec0.groupId(), rec0.pageId(), IoStatisticsHolderNoOp.INSTANCE, true);
 
                         try {
                             long addr = pageMem.writeLock(rec0.groupId(), rec0.pageId(), page, true);

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/RowStore.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/RowStore.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/RowStore.java
index 6900b7e..91fd207 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/RowStore.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/RowStore.java
@@ -25,6 +25,7 @@ import org.apache.ignite.internal.processors.cache.GridCacheSharedContext;
 import org.apache.ignite.internal.processors.cache.persistence.freelist.FreeList;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
 import org.apache.ignite.internal.processors.query.GridQueryRowCacheCleaner;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 
 /**
  * Data store for H2 rows.
@@ -69,19 +70,19 @@ public class RowStore {
      * @param link Row link.
      * @throws IgniteCheckedException If failed.
      */
-    public void removeRow(long link) throws IgniteCheckedException {
+    public void removeRow(long link, IoStatisticsHolder statHolder) throws IgniteCheckedException {
         assert link != 0;
 
         if (rowCacheCleaner != null)
             rowCacheCleaner.remove(link);
 
         if (!persistenceEnabled)
-            freeList.removeDataRowByLink(link);
+            freeList.removeDataRowByLink(link, statHolder);
         else {
             ctx.database().checkpointReadLock();
 
             try {
-                freeList.removeDataRowByLink(link);
+                freeList.removeDataRowByLink(link, statHolder);
             }
             finally {
                 ctx.database().checkpointReadUnlock();
@@ -93,14 +94,14 @@ public class RowStore {
      * @param row Row.
      * @throws IgniteCheckedException If failed.
      */
-    public void addRow(CacheDataRow row) throws IgniteCheckedException {
+    public void addRow(CacheDataRow row, IoStatisticsHolder statHolder) throws IgniteCheckedException {
         if (!persistenceEnabled)
-            freeList.insertDataRow(row);
+            freeList.insertDataRow(row, statHolder);
         else {
             ctx.database().checkpointReadLock();
 
             try {
-                freeList.insertDataRow(row);
+                freeList.insertDataRow(row, statHolder);
 
                 assert row.link() != 0L;
             }
@@ -116,13 +117,13 @@ public class RowStore {
      * @return {@code True} if was able to update row.
      * @throws IgniteCheckedException If failed.
      */
-    public boolean updateRow(long link, CacheDataRow row) throws IgniteCheckedException {
+    public boolean updateRow(long link, CacheDataRow row, IoStatisticsHolder statHolder) throws IgniteCheckedException {
         assert !persistenceEnabled || ctx.database().checkpointLockIsHeldByThread();
 
         if (rowCacheCleaner != null)
             rowCacheCleaner.remove(link);
 
-        return freeList.updateDataRow(link, row);
+        return freeList.updateDataRow(link, row, statHolder);
     }
 
     /**
@@ -133,14 +134,15 @@ public class RowStore {
      * @param arg Page handler argument.
      * @throws IgniteCheckedException If failed.
      */
-    public <S, R> void updateDataRow(long link, PageHandler<S, R> pageHnd, S arg) throws IgniteCheckedException {
+    public <S, R> void updateDataRow(long link, PageHandler<S, R> pageHnd, S arg,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
         if (!persistenceEnabled)
-            freeList.updateDataRow(link, pageHnd, arg);
+            freeList.updateDataRow(link, pageHnd, arg, statHolder);
         else {
             ctx.database().checkpointReadLock();
 
             try {
-                freeList.updateDataRow(link, pageHnd, arg);
+                freeList.updateDataRow(link, pageHnd, arg, statHolder);
             }
             finally {
                 ctx.database().checkpointReadUnlock();

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
index 4e1f783..60aefb9 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/AbstractFreeList.java
@@ -40,6 +40,8 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.LongLi
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseBag;
 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.stat.IoStatisticsHolder;
+import org.apache.ignite.internal.stat.IoStatisticsHolderNoOp;
 import org.apache.ignite.internal.util.typedef.internal.U;
 
 /**
@@ -96,7 +98,8 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
             PageIO iox,
             Boolean walPlc,
             T row,
-            int itemId)
+            int itemId,
+            IoStatisticsHolder statHolder)
             throws IgniteCheckedException {
             AbstractDataPageIO<T> io = (AbstractDataPageIO<T>)iox;
 
@@ -142,7 +145,8 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
             PageIO iox,
             Boolean walPlc,
             T row,
-            int written)
+            int written,
+            IoStatisticsHolder statHolder)
             throws IgniteCheckedException {
             AbstractDataPageIO<T> io = (AbstractDataPageIO<T>)iox;
 
@@ -161,7 +165,7 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
             if (newFreeSpace > MIN_PAGE_FREE_SPACE) {
                 int bucket = bucket(newFreeSpace, false);
 
-                put(null, pageId, page, pageAddr, bucket);
+                put(null, pageId, page, pageAddr, bucket, statHolder);
             }
 
             if (written == rowSize)
@@ -275,7 +279,8 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
             PageIO iox,
             Boolean walPlc,
             ReuseBag reuseBag,
-            int itemId)
+            int itemId,
+            IoStatisticsHolder statHolder)
             throws IgniteCheckedException {
             AbstractDataPageIO<T> io = (AbstractDataPageIO<T>)iox;
 
@@ -302,7 +307,7 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
                         // It is possible that page was concurrently taken for put, in this case put will handle bucket change.
                         pageId = maskPartId ? PageIdUtils.maskPartitionId(pageId) : pageId;
 
-                        putIsNeeded = removeDataPage(pageId, page, pageAddr, io, oldBucket);
+                        putIsNeeded = removeDataPage(pageId, page, pageAddr, io, oldBucket, statHolder);
                     }
                 }
 
@@ -313,7 +318,7 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
                         reuseBag.addFreePage(recyclePage(pageId, page, pageAddr, null));
                 }
                 else if (putIsNeeded)
-                    put(null, pageId, page, pageAddr, newBucket);
+                    put(null, pageId, page, pageAddr, newBucket, statHolder);
             }
 
             // For common case boxed 0L will be cached inside of Long, so no garbage will be produced.
@@ -465,7 +470,7 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
     }
 
     /** {@inheritDoc} */
-    @Override public void insertDataRow(T row) throws IgniteCheckedException {
+    @Override public void insertDataRow(T row, IoStatisticsHolder statHolder) throws IgniteCheckedException {
         int rowSize = row.size();
 
         int written = 0;
@@ -479,7 +484,7 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
             long pageId = 0L;
 
             for (int b = remaining < MIN_SIZE_FOR_DATA_PAGE ? bucket(remaining, false) + 1 : REUSE_BUCKET; b < BUCKETS; b++) {
-                pageId = takeEmptyPage(b, ioVersions());
+                pageId = takeEmptyPage(b, ioVersions(), statHolder);
 
                 if (pageId != 0L)
                     break;
@@ -493,11 +498,11 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
                 initIo = ioVersions().latest();
             }
             else if (PageIdUtils.tag(pageId) != PageIdAllocator.FLAG_DATA)
-                pageId = initReusedPage(pageId, row.partition());
+                pageId = initReusedPage(pageId, row.partition(), statHolder);
             else
                 pageId = PageIdUtils.changePartitionId(pageId, (row.partition()));
 
-            written = write(pageId, writeRow, initIo, row, written, FAIL_I);
+            written = write(pageId, writeRow, initIo, row, written, FAIL_I, statHolder);
 
             assert written != FAIL_I; // We can't fail here.
         }
@@ -507,12 +512,14 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
     /**
      * @param reusedPageId Reused page id.
      * @param partId Partition id.
+     * @param statHolder Statistics holder to track IO operations.
      * @return Prepared page id.
      *
      * @see PagesList#initReusedPage(long, long, long, int, byte, PageIO)
      */
-    private long initReusedPage(long reusedPageId, int partId) throws IgniteCheckedException {
-        long reusedPage = acquirePage(reusedPageId);
+    private long initReusedPage(long reusedPageId, int partId,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
+        long reusedPage = acquirePage(reusedPageId, statHolder);
         try {
             long reusedPageAddr = writeLock(reusedPageId, reusedPage);
 
@@ -532,13 +539,14 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
     }
 
     /** {@inheritDoc} */
-    @Override public boolean updateDataRow(long link, T row) throws IgniteCheckedException {
+    @Override public boolean updateDataRow(long link, T row,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
         assert link != 0;
 
         long pageId = PageIdUtils.pageId(link);
         int itemId = PageIdUtils.itemId(link);
 
-        Boolean updated = write(pageId, updateRow, row, itemId, null);
+        Boolean updated = write(pageId, updateRow, row, itemId, null, statHolder);
 
         assert updated != null; // Can't fail here.
 
@@ -546,13 +554,14 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
     }
 
     /** {@inheritDoc} */
-    @Override public <S, R> R updateDataRow(long link, PageHandler<S, R> pageHnd, S arg) throws IgniteCheckedException {
+    @Override public <S, R> R updateDataRow(long link, PageHandler<S, R> pageHnd, S arg,
+        IoStatisticsHolder statHolder) throws IgniteCheckedException {
         assert link != 0;
 
         long pageId = PageIdUtils.pageId(link);
         int itemId = PageIdUtils.itemId(link);
 
-        R updRes = write(pageId, pageHnd, arg, itemId, null);
+        R updRes = write(pageId, pageHnd, arg, itemId, null, statHolder);
 
         assert updRes != null; // Can't fail here.
 
@@ -560,7 +569,7 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
     }
 
     /** {@inheritDoc} */
-    @Override public void removeDataRowByLink(long link) throws IgniteCheckedException {
+    @Override public void removeDataRowByLink(long link, IoStatisticsHolder statHolder) throws IgniteCheckedException {
         assert link != 0;
 
         long pageId = PageIdUtils.pageId(link);
@@ -568,7 +577,7 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
 
         ReuseBag bag = new LongListReuseBag();
 
-        long nextLink = write(pageId, rmvRow, bag, itemId, FAIL_L);
+        long nextLink = write(pageId, rmvRow, bag, itemId, FAIL_L, statHolder);
 
         assert nextLink != FAIL_L; // Can't fail here.
 
@@ -578,7 +587,7 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
             itemId = PageIdUtils.itemId(nextLink);
             pageId = PageIdUtils.pageId(nextLink);
 
-            nextLink = write(pageId, rmvRow, bag, itemId, FAIL_L);
+            nextLink = write(pageId, rmvRow, bag, itemId, FAIL_L, statHolder);
 
             assert nextLink != FAIL_L; // Can't fail here.
         }
@@ -612,14 +621,14 @@ public abstract class AbstractFreeList<T extends Storable> extends PagesList imp
     @Override public void addForRecycle(ReuseBag bag) throws IgniteCheckedException {
         assert reuseList == this : "not allowed to be a reuse list";
 
-        put(bag, 0, 0, 0L, REUSE_BUCKET);
+        put(bag, 0, 0, 0L, REUSE_BUCKET, IoStatisticsHolderNoOp.INSTANCE);
     }
 
     /** {@inheritDoc} */
     @Override public long takeRecycledPage() throws IgniteCheckedException {
         assert reuseList == this : "not allowed to be a reuse list";
 
-        return takeEmptyPage(REUSE_BUCKET, null);
+        return takeEmptyPage(REUSE_BUCKET, null, IoStatisticsHolderNoOp.INSTANCE);
     }
 
     /** {@inheritDoc} */

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeListImpl.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeListImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeListImpl.java
index 4aa9d88..625c0b1 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeListImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/CacheFreeListImpl.java
@@ -27,6 +27,7 @@ import org.apache.ignite.internal.processors.cache.persistence.tree.io.AbstractD
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.DataPageIO;
 import org.apache.ignite.internal.processors.cache.persistence.tree.io.IOVersions;
 import org.apache.ignite.internal.processors.cache.persistence.tree.reuse.ReuseList;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 import org.apache.ignite.internal.util.typedef.internal.U;
 
 /**
@@ -55,8 +56,8 @@ public class CacheFreeListImpl extends AbstractFreeList<CacheDataRow> {
     }
 
     /** {@inheritDoc} */
-    @Override public void insertDataRow(CacheDataRow row) throws IgniteCheckedException {
-        super.insertDataRow(row);
+    @Override public void insertDataRow(CacheDataRow row, IoStatisticsHolder statHolder) throws IgniteCheckedException {
+        super.insertDataRow(row, statHolder);
 
         assert row.key().partition() == PageIdUtils.partId(row.link()) :
             "Constructed a link with invalid partition ID [partId=" + row.key().partition() +

http://git-wip-us.apache.org/repos/asf/ignite/blob/1eb57502/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/FreeList.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/FreeList.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/FreeList.java
index e73124e..e28d421 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/FreeList.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/freelist/FreeList.java
@@ -21,6 +21,7 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.internal.processors.cache.persistence.Storable;
 import org.apache.ignite.internal.processors.cache.persistence.tree.util.PageHandler;
+import org.apache.ignite.internal.stat.IoStatisticsHolder;
 
 /**
  */
@@ -29,7 +30,7 @@ public interface FreeList<T extends Storable> {
      * @param row Row.
      * @throws IgniteCheckedException If failed.
      */
-    public void insertDataRow(T row) throws IgniteCheckedException;
+    public void insertDataRow(T row, IoStatisticsHolder statHolder) throws IgniteCheckedException;
 
     /**
      * @param link Row link.
@@ -37,7 +38,7 @@ public interface FreeList<T extends Storable> {
      * @return {@code True} if was able to update row.
      * @throws IgniteCheckedException If failed.
      */
-    public boolean updateDataRow(long link, T row) throws IgniteCheckedException;
+    public boolean updateDataRow(long link, T row, IoStatisticsHolder statHolder) throws IgniteCheckedException;
 
     /**
      * @param link Row link.
@@ -48,13 +49,14 @@ public interface FreeList<T extends Storable> {
      * @return Result.
      * @throws IgniteCheckedException If failed.
      */
-    public <S, R>  R updateDataRow(long link, PageHandler<S, R> pageHnd, S arg) throws IgniteCheckedException;
+    public <S, R> R updateDataRow(long link, PageHandler<S, R> pageHnd, S arg, IoStatisticsHolder statHolder)
+        throws IgniteCheckedException;
 
     /**
      * @param link Row link.
      * @throws IgniteCheckedException If failed.
      */
-    public void removeDataRowByLink(long link) throws IgniteCheckedException;
+    public void removeDataRowByLink(long link, IoStatisticsHolder statHolder) throws IgniteCheckedException;
 
     /**
      * @param log Logger.