You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sb...@apache.org on 2016/11/22 11:40:20 UTC

[31/50] [abbrv] ignite git commit: IGNITE-3443 Implemented metrics for queries monitoring.

IGNITE-3443 Implemented metrics for queries monitoring.


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

Branch: refs/heads/master
Commit: 0234f67390c88dceefd6e62de98adb922b4ba9ac
Parents: 3eacc0b
Author: Alexey Kuznetsov <ak...@apache.org>
Authored: Mon Nov 21 17:40:50 2016 +0700
Committer: Alexey Kuznetsov <ak...@apache.org>
Committed: Mon Nov 21 17:40:50 2016 +0700

----------------------------------------------------------------------
 .../java/org/apache/ignite/IgniteCache.java     |  21 +-
 .../ignite/cache/query/QueryDetailMetrics.java  |  98 ++++
 .../apache/ignite/cache/query/QueryMetrics.java |   2 +-
 .../configuration/CacheConfiguration.java       |  30 +
 .../processors/cache/GridCacheUtilityKey.java   |   6 +-
 .../processors/cache/IgniteCacheProxy.java      |  59 +-
 .../processors/cache/query/CacheQuery.java      |   1 -
 .../processors/cache/query/CacheQueryType.java  |   2 +-
 .../cache/query/GridCacheQueryAdapter.java      |  10 -
 .../GridCacheQueryDetailMetricsAdapter.java     | 248 ++++++++
 .../query/GridCacheQueryDetailMetricsKey.java   |  82 +++
 .../query/GridCacheQueryFutureAdapter.java      |   2 -
 .../cache/query/GridCacheQueryManager.java      | 149 ++++-
 .../query/GridCacheQueryMetricsAdapter.java     |  27 +-
 .../processors/query/GridQueryProcessor.java    | 102 ++--
 .../internal/util/GridBoundedPriorityQueue.java |  68 +++
 .../cache/VisorCacheQueryDetailMetrics.java     | 167 ++++++
 ...sorCacheQueryDetailMetricsCollectorTask.java | 146 +++++
 .../VisorCacheResetQueryDetailMetricsTask.java  |  71 +++
 .../cache/VisorCacheResetQueryMetricsTask.java  |   8 +-
 .../internal/visor/util/VisorTaskUtils.java     |  26 +-
 .../resources/META-INF/classnames.properties    |  75 ++-
 .../utils/GridBoundedPriorityQueueSelfTest.java | 114 ++++
 .../multijvm/IgniteCacheProcessProxy.java       |  16 +
 .../testsuites/IgniteLangSelfTestSuite.java     |   4 +-
 ...CacheAbstractQueryDetailMetricsSelfTest.java | 572 +++++++++++++++++++
 .../CacheAbstractQueryMetricsSelfTest.java      | 305 ++++++----
 .../CacheLocalQueryDetailMetricsSelfTest.java   |  33 ++
 ...edQueryDetailMetricsDistributedSelfTest.java |  33 ++
 ...titionedQueryDetailMetricsLocalSelfTest.java |  33 ++
 ...edQueryDetailMetricsDistributedSelfTest.java |  33 ++
 ...plicatedQueryDetailMetricsLocalSelfTest.java |  33 ++
 .../IgniteCacheQuerySelfTestSuite2.java         |  12 +
 .../commands/cache/VisorCacheCommand.scala      |   7 +-
 34 files changed, 2338 insertions(+), 257 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/IgniteCache.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/IgniteCache.java b/modules/core/src/main/java/org/apache/ignite/IgniteCache.java
index 2290fc5..7eb6e91 100644
--- a/modules/core/src/main/java/org/apache/ignite/IgniteCache.java
+++ b/modules/core/src/main/java/org/apache/ignite/IgniteCache.java
@@ -43,6 +43,7 @@ import org.apache.ignite.cache.CacheMode;
 import org.apache.ignite.cache.CachePeekMode;
 import org.apache.ignite.cache.query.Query;
 import org.apache.ignite.cache.query.QueryCursor;
+import org.apache.ignite.cache.query.QueryDetailMetrics;
 import org.apache.ignite.cache.query.QueryMetrics;
 import org.apache.ignite.cache.query.ScanQuery;
 import org.apache.ignite.cache.query.SpiQuery;
@@ -326,6 +327,24 @@ public interface IgniteCache<K, V> extends javax.cache.Cache<K, V>, IgniteAsyncS
     public QueryMetrics queryMetrics();
 
     /**
+     * Reset query metrics.
+     */
+    public void resetQueryMetrics();
+
+    /**
+     * Gets query detail metrics.
+     * Query detail metrics could be enabled via {@link CacheConfiguration#setQueryDetailMetricsSize(int)} method.
+     *
+     * @return Metrics.
+     */
+    public Collection<? extends QueryDetailMetrics> queryDetailMetrics();
+
+    /**
+     * Reset query detail metrics.
+     */
+    public void resetQueryDetailMetrics();
+
+    /**
      * Attempts to evict all entries associated with keys. Note,
      * that entry will be evicted only if it's not used (not
      * participating in any locks or transactions).
@@ -335,7 +354,7 @@ public interface IgniteCache<K, V> extends javax.cache.Cache<K, V>, IgniteAsyncS
     public void localEvict(Collection<? extends K> keys);
 
     /**
-     * Peeks at in-memory cached value using default optinal peek mode.
+     * Peeks at in-memory cached value using default optional peek mode.
      * <p>
      * This method will not load value from any persistent store or from a remote node.
      * <h2 class="header">Transactions</h2>

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/cache/query/QueryDetailMetrics.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/query/QueryDetailMetrics.java b/modules/core/src/main/java/org/apache/ignite/cache/query/QueryDetailMetrics.java
new file mode 100644
index 0000000..0b01880
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/cache/query/QueryDetailMetrics.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.cache.query;
+
+import org.apache.ignite.configuration.CacheConfiguration;
+
+/**
+ * Query metrics aggregated by query type and its textual representation.
+ *
+ * Query detail metrics could be enabled via {@link CacheConfiguration#setQueryDetailMetricsSize(int)} method.
+ */
+public interface QueryDetailMetrics {
+    /**
+     * @return Query type.
+     */
+    public String queryType();
+
+    /**
+     * @return Textual representation of query.
+     */
+    public String query();
+
+    /**
+     * @return Cache where query was executed.
+     */
+    public String cache();
+
+    /**
+     * Gets total number execution of query.
+     *
+     * @return Number of executions.
+     */
+    public int executions();
+
+    /**
+     * Gets number of completed execution of query.
+     *
+     * @return Number of completed executions.
+     */
+    public int completions();
+
+    /**
+     * Gets number of times a query execution failed.
+     *
+     * @return Number of times a query execution failed.
+     */
+    public int failures();
+
+    /**
+     * Gets minimum execution time of query.
+     *
+     * @return Minimum execution time of query.
+     */
+    public long minimumTime();
+
+    /**
+     * Gets maximum execution time of query.
+     *
+     * @return Maximum execution time of query.
+     */
+    public long maximumTime();
+
+    /**
+     * Gets average execution time of query.
+     *
+     * @return Average execution time of query.
+     */
+    public double averageTime();
+
+    /**
+     * Gets total time of all query executions.
+     *
+     * @return Total time of all query executions.
+     */
+    public long totalTime();
+
+    /**
+     * Gets latest query start time.
+     *
+     * @return Latest time query was stared.
+     */
+    public long lastStartTime();
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/cache/query/QueryMetrics.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/cache/query/QueryMetrics.java b/modules/core/src/main/java/org/apache/ignite/cache/query/QueryMetrics.java
index e376561..d0f0a50 100644
--- a/modules/core/src/main/java/org/apache/ignite/cache/query/QueryMetrics.java
+++ b/modules/core/src/main/java/org/apache/ignite/cache/query/QueryMetrics.java
@@ -59,4 +59,4 @@ public interface QueryMetrics {
      * @return Total number of times a query execution failed.
      */
     public int fails();
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
index f65bf52..f9c114b 100644
--- a/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
+++ b/modules/core/src/main/java/org/apache/ignite/configuration/CacheConfiguration.java
@@ -209,6 +209,9 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
     /** Default timeout after which long query warning will be printed. */
     public static final long DFLT_LONG_QRY_WARN_TIMEOUT = 3000;
 
+    /** Default number of queries detail metrics to collect. */
+    public static final int DFLT_QRY_DETAIL_METRICS_SIZE = 0;
+
     /** Default size for onheap SQL row cache size. */
     public static final int DFLT_SQL_ONHEAP_ROW_CACHE_SIZE = 10 * 1024;
 
@@ -361,6 +364,9 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
     /** */
     private long longQryWarnTimeout = DFLT_LONG_QRY_WARN_TIMEOUT;
 
+    /** */
+    private int qryDetailMetricsSz = DFLT_QRY_DETAIL_METRICS_SIZE;
+
     /**
      * Flag indicating whether data can be read from backup.
      * If {@code false} always get data from primary node (never from backup).
@@ -468,6 +474,7 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
         nodeFilter = cc.getNodeFilter();
         pluginCfgs = cc.getPluginConfigurations();
         qryEntities = cc.getQueryEntities() == Collections.<QueryEntity>emptyList() ? null : cc.getQueryEntities();
+        qryDetailMetricsSz = cc.getQueryDetailMetricsSize();
         readFromBackup = cc.isReadFromBackup();
         rebalanceBatchSize = cc.getRebalanceBatchSize();
         rebalanceBatchesPrefetchCount = cc.getRebalanceBatchesPrefetchCount();
@@ -1824,6 +1831,29 @@ public class CacheConfiguration<K, V> extends MutableConfiguration<K, V> {
     }
 
     /**
+     * Gets size of queries detail metrics that will be stored in memory for monitoring purposes.
+     * If {@code 0} then history will not be collected.
+     * Note, larger number may lead to higher memory consumption.
+     *
+     * @return Maximum number of query metrics that will be stored in memory.
+     */
+    public int getQueryDetailMetricsSize() {
+        return qryDetailMetricsSz;
+    }
+
+    /**
+     * Sets size of queries detail metrics that will be stored in memory for monitoring purposes.
+     *
+     * @param qryDetailMetricsSz Maximum number of latest queries metrics that will be stored in memory.
+     * @return {@code this} for chaining.
+     */
+    public CacheConfiguration<K, V> setQueryDetailMetricsSize(int qryDetailMetricsSz) {
+        this.qryDetailMetricsSz = qryDetailMetricsSz;
+
+        return this;
+    }
+
+    /**
      * Gets custom name of the sql schema. If custom sql schema is not set then {@code null} will be returned and
      * quoted case sensitive name will be used as sql schema.
      *

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtilityKey.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtilityKey.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtilityKey.java
index 48b6b45..8110170 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtilityKey.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/GridCacheUtilityKey.java
@@ -27,8 +27,8 @@ public abstract class GridCacheUtilityKey<K extends GridCacheUtilityKey> impleme
     private static final long serialVersionUID = 0L;
 
     /** {@inheritDoc} */
-    @SuppressWarnings("unchecked") @Override
-    public final boolean equals(Object obj) {
+    @SuppressWarnings("unchecked")
+    @Override public final boolean equals(Object obj) {
         return obj == this || obj != null && obj.getClass() == getClass() && equalsx((K)obj);
     }
 
@@ -42,4 +42,4 @@ public abstract class GridCacheUtilityKey<K extends GridCacheUtilityKey> impleme
 
     /** {@inheritDoc} */
     public abstract int hashCode();
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxy.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxy.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxy.java
index 297ec68..f87fa1d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxy.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/IgniteCacheProxy.java
@@ -51,6 +51,7 @@ import org.apache.ignite.cache.CachePeekMode;
 import org.apache.ignite.cache.query.ContinuousQuery;
 import org.apache.ignite.cache.query.Query;
 import org.apache.ignite.cache.query.QueryCursor;
+import org.apache.ignite.cache.query.QueryDetailMetrics;
 import org.apache.ignite.cache.query.QueryMetrics;
 import org.apache.ignite.cache.query.ScanQuery;
 import org.apache.ignite.cache.query.SpiQuery;
@@ -63,10 +64,10 @@ import org.apache.ignite.internal.AsyncSupportAdapter;
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.IgniteEx;
 import org.apache.ignite.internal.IgniteInternalFuture;
-import org.apache.ignite.internal.binary.BinaryUtils;
 import org.apache.ignite.internal.processors.affinity.AffinityTopologyVersion;
 import org.apache.ignite.internal.processors.cache.query.CacheQuery;
 import org.apache.ignite.internal.processors.cache.query.CacheQueryFuture;
+import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType;
 import org.apache.ignite.internal.processors.query.GridQueryProcessor;
 import org.apache.ignite.internal.util.GridCloseableIteratorAdapter;
 import org.apache.ignite.internal.util.GridEmptyIterator;
@@ -243,6 +244,7 @@ public class IgniteCacheProxy<K, V> extends AsyncSupportAdapter<IgniteCache<K, V
         }
     }
 
+    /** {@inheritDoc} */
     @Override public CacheMetrics localMetrics() {
         GridCacheGateway<K, V> gate = this.gate;
 
@@ -270,6 +272,7 @@ public class IgniteCacheProxy<K, V> extends AsyncSupportAdapter<IgniteCache<K, V
         }
     }
 
+    /** {@inheritDoc} */
     @Override public CacheMetricsMXBean localMxBean() {
         GridCacheGateway<K, V> gate = this.gate;
 
@@ -491,8 +494,8 @@ public class IgniteCacheProxy<K, V> extends AsyncSupportAdapter<IgniteCache<K, V
         if (grp != null)
             qry.projection(grp);
 
-        final GridCloseableIterator<R> iter = ctx.kernalContext().query().executeQuery(ctx,
-            new IgniteOutClosureX<GridCloseableIterator<R>>() {
+        final GridCloseableIterator<R> iter = ctx.kernalContext().query().executeQuery(GridCacheQueryType.SCAN,
+            ctx.name(), ctx, new IgniteOutClosureX<GridCloseableIterator<R>>() {
                 @Override public GridCloseableIterator<R> applyx() throws IgniteCheckedException {
                     final GridCloseableIterator iter0 = qry.executeScanQuery();
 
@@ -520,7 +523,7 @@ public class IgniteCacheProxy<K, V> extends AsyncSupportAdapter<IgniteCache<K, V
                         }
                     };
                 }
-            }, false);
+            }, true);
 
         return new QueryCursorImpl<>(iter);
     }
@@ -547,7 +550,7 @@ public class IgniteCacheProxy<K, V> extends AsyncSupportAdapter<IgniteCache<K, V
             if (grp != null)
                 qry.projection(grp);
 
-            fut = ctx.kernalContext().query().executeQuery(ctx,
+            fut = ctx.kernalContext().query().executeQuery(GridCacheQueryType.TEXT, p.getText(), ctx,
                 new IgniteOutClosureX<CacheQueryFuture<Map.Entry<K, V>>>() {
                     @Override public CacheQueryFuture<Map.Entry<K, V>> applyx() throws IgniteCheckedException {
                         return qry.execute();
@@ -560,8 +563,8 @@ public class IgniteCacheProxy<K, V> extends AsyncSupportAdapter<IgniteCache<K, V
             if (grp != null)
                 qry.projection(grp);
 
-            fut = ctx.kernalContext().query().executeQuery(ctx,
-                new IgniteOutClosureX<CacheQueryFuture<Cache.Entry<K, V>>>() {
+            fut = ctx.kernalContext().query().executeQuery(GridCacheQueryType.SPI, filter.getClass().getSimpleName(),
+                ctx, new IgniteOutClosureX<CacheQueryFuture<Cache.Entry<K, V>>>() {
                     @Override public CacheQueryFuture<Cache.Entry<K, V>> applyx() throws IgniteCheckedException {
                         return qry.execute(((SpiQuery)filter).getArgs());
                     }
@@ -887,6 +890,48 @@ public class IgniteCacheProxy<K, V> extends AsyncSupportAdapter<IgniteCache<K, V
     }
 
     /** {@inheritDoc} */
+    @Override public void resetQueryMetrics() {
+        GridCacheGateway<K, V> gate = this.gate;
+
+        CacheOperationContext prev = onEnter(gate, opCtx);
+
+        try {
+            delegate.context().queries().resetMetrics();
+        }
+        finally {
+            onLeave(gate, prev);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public Collection<? extends QueryDetailMetrics> queryDetailMetrics() {
+        GridCacheGateway<K, V> gate = this.gate;
+
+        CacheOperationContext prev = onEnter(gate, opCtx);
+
+        try {
+            return delegate.context().queries().detailMetrics();
+        }
+        finally {
+            onLeave(gate, prev);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void resetQueryDetailMetrics() {
+        GridCacheGateway<K, V> gate = this.gate;
+
+        CacheOperationContext prev = onEnter(gate, opCtx);
+
+        try {
+            delegate.context().queries().resetDetailMetrics();
+        }
+        finally {
+            onLeave(gate, prev);
+        }
+    }
+
+    /** {@inheritDoc} */
     @Override public void localEvict(Collection<? extends K> keys) {
         GridCacheGateway<K, V> gate = this.gate;
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java
index 3fa041b..a7dbe2a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQuery.java
@@ -17,7 +17,6 @@
 
 package org.apache.ignite.internal.processors.cache.query;
 
-import java.util.Map;
 import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.cache.affinity.AffinityKey;
 import org.apache.ignite.cache.query.Query;

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQueryType.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQueryType.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQueryType.java
index bb8bc33..fa0f3df 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQueryType.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/CacheQueryType.java
@@ -44,4 +44,4 @@ public enum CacheQueryType {
 
     /** SPI query. */
     SPI
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryAdapter.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryAdapter.java
index f65b733..2355591 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryAdapter.java
@@ -434,16 +434,6 @@ public class GridCacheQueryAdapter<T> implements CacheQuery<T> {
             throw new IgniteCheckedException("Indexing is disabled for cache: " + cctx.cache().name());
     }
 
-    /**
-     * @param res Query result.
-     * @param err Error or {@code null} if query executed successfully.
-     * @param startTime Start time.
-     * @param duration Duration.
-     */
-    public void onCompleted(Object res, Throwable err, long startTime, long duration) {
-        GridQueryProcessor.onCompleted(cctx, res, err, startTime, duration, log);
-    }
-
     /** {@inheritDoc} */
     @Override public CacheQueryFuture<T> execute(@Nullable Object... args) {
         return execute0(null, args);

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryDetailMetricsAdapter.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryDetailMetricsAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryDetailMetricsAdapter.java
new file mode 100644
index 0000000..a477360
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryDetailMetricsAdapter.java
@@ -0,0 +1,248 @@
+/*
+ * 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.query;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import org.apache.ignite.cache.query.QueryDetailMetrics;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Adapter for {@link QueryDetailMetrics}.
+ */
+public class GridCacheQueryDetailMetricsAdapter implements QueryDetailMetrics, Externalizable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Query type to track metrics. */
+    private GridCacheQueryType qryType;
+
+    /** Textual query representation. */
+    private String qry;
+
+    /** Cache name. */
+    private String cache;
+
+    /** Number of executions. */
+    private int execs;
+
+    /** Number of completions executions. */
+    private int completions;
+
+    /** Number of failures. */
+    private int failures;
+
+    /** Minimum time of execution. */
+    private long minTime = -1;
+
+    /** Maximum time of execution. */
+    private long maxTime;
+
+    /** Sum of execution time of completions time. */
+    private long totalTime;
+
+    /** Sum of execution time of completions time. */
+    private long lastStartTime;
+
+    /** Cached metrics group key.*/
+    private GridCacheQueryDetailMetricsKey key;
+
+    /**
+     * Required by {@link Externalizable}.
+     */
+    public GridCacheQueryDetailMetricsAdapter() {
+        // No-op.
+    }
+
+    /**
+     * Constructor with metrics.
+     *
+     * @param qryType Query type.
+     * @param qry Textual query representation.
+     * @param cache Cache name where query was executed.
+     * @param startTime Duration of queue execution.
+     * @param duration Duration of queue execution.
+     * @param failed {@code True} query executed unsuccessfully {@code false} otherwise.
+     */
+    public GridCacheQueryDetailMetricsAdapter(GridCacheQueryType qryType, String qry, String cache, long startTime,
+        long duration, boolean failed) {
+        this.qryType = qryType;
+        this.qry = qryType == GridCacheQueryType.SCAN && qry == null ? cache : qry;
+        this.cache = cache;
+
+        if (failed) {
+            execs = 1;
+            failures = 1;
+        }
+        else {
+            execs = 1;
+            completions = 1;
+            totalTime = duration;
+            minTime = duration;
+            maxTime = duration;
+        }
+
+        lastStartTime = startTime;
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param qryType Query type.
+     * @param qry Textual query representation.
+     * @param cache Cache name where query was executed.
+     */
+    public GridCacheQueryDetailMetricsAdapter(GridCacheQueryType qryType, String qry, String cache,
+        int execs, int completions, int failures, long minTime, long maxTime, long totalTime, long lastStartTime,
+        GridCacheQueryDetailMetricsKey key) {
+        this.qryType = qryType;
+        this.qry = qry;
+        this.cache = cache;
+        this.execs = execs;
+        this.completions = completions;
+        this.failures = failures;
+        this.minTime = minTime;
+        this.maxTime = maxTime;
+        this.totalTime = totalTime;
+        this.lastStartTime = lastStartTime;
+        this.key = key;
+    }
+
+    /**
+     * @return Metrics group key.
+     */
+    public GridCacheQueryDetailMetricsKey key() {
+        if (key == null)
+            key = new GridCacheQueryDetailMetricsKey(qryType, qry);
+
+        return key;
+    }
+
+    /**
+     * Aggregate metrics.
+     *
+     * @param m Other metrics to take into account.
+     * @return Aggregated metrics.
+     */
+    public GridCacheQueryDetailMetricsAdapter aggregate(QueryDetailMetrics m) {
+        return new GridCacheQueryDetailMetricsAdapter(
+            qryType,
+            qry,
+            m.cache(),
+            execs + m.executions(),
+            completions + m.completions(),
+            failures + m.failures(),
+            minTime < 0 || minTime > m.minimumTime() ? m.minimumTime() : minTime,
+            maxTime < m.maximumTime() ? m.maximumTime() : maxTime,
+            totalTime + m.totalTime(),
+            lastStartTime < m.lastStartTime() ? m.lastStartTime() : lastStartTime,
+            key
+        );
+    }
+
+    /** {@inheritDoc} */
+    @Override public String queryType() {
+        return qryType.name();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String query() {
+        return qry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String cache() {
+        return cache;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int executions() {
+        return execs;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int completions() {
+        return completions;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int failures() {
+        return failures;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long minimumTime() {
+        return minTime < 0 ? 0 : minTime;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long maximumTime() {
+        return maxTime;
+    }
+
+    /** {@inheritDoc} */
+    @Override public double averageTime() {
+        double val = completions;
+
+        return val > 0 ? totalTime / val : 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long totalTime() {
+        return totalTime;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long lastStartTime() {
+        return lastStartTime;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void writeExternal(ObjectOutput out) throws IOException {
+        U.writeEnum(out, qryType);
+        U.writeString(out, qry);
+        U.writeString(out, cache);
+        out.writeInt(execs);
+        out.writeInt(completions);
+        out.writeLong(minTime);
+        out.writeLong(maxTime);
+        out.writeLong(totalTime);
+        out.writeLong(lastStartTime);
+    }
+
+    /** {@inheritDoc} */
+    @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+        qryType = GridCacheQueryType.fromOrdinal(in.readByte());
+        qry = U.readString(in);
+        cache = U.readString(in);
+        execs = in.readInt();
+        completions = in.readInt();
+        minTime = in.readLong();
+        maxTime = in.readLong();
+        totalTime = in.readLong();
+        lastStartTime = in.readLong();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(GridCacheQueryDetailMetricsAdapter.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryDetailMetricsKey.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryDetailMetricsKey.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryDetailMetricsKey.java
new file mode 100644
index 0000000..eb70cd1
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryDetailMetricsKey.java
@@ -0,0 +1,82 @@
+/*
+ * 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.query;
+
+import org.apache.ignite.internal.util.typedef.F;
+
+/**
+ * Immutable query metrics key used to group metrics.
+ */
+public class GridCacheQueryDetailMetricsKey {
+    /** Query type to track metrics. */
+    private final GridCacheQueryType qryType;
+
+    /** Textual query representation. */
+    private final String qry;
+
+    /** Pre-calculated hash code. */
+    private final int hash;
+
+    /**
+     * Constructor.
+     *
+     * @param qryType Query type.
+     * @param qry Textual query representation.
+     */
+    public GridCacheQueryDetailMetricsKey(GridCacheQueryType qryType, String qry) {
+        assert qryType != null;
+        assert qryType != GridCacheQueryType.SQL_FIELDS || qry != null;
+
+        this.qryType = qryType;
+        this.qry = qry;
+
+        hash = 31 * qryType.hashCode() + (qry != null ? qry.hashCode() : 0);
+    }
+
+    /**
+     * @return Query type.
+     */
+    public GridCacheQueryType getQueryType() {
+        return qryType;
+    }
+
+    /**
+     * @return Textual representation of query.
+     */
+    public String getQuery() {
+        return qry;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return hash;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o)
+            return true;
+
+        if (o == null || getClass() != o.getClass())
+            return false;
+
+        GridCacheQueryDetailMetricsKey other = (GridCacheQueryDetailMetricsKey)o;
+
+        return qryType == other.qryType && F.eq(qry, other.qry);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryFutureAdapter.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryFutureAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryFutureAdapter.java
index db519f5..4b8915c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryFutureAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryFutureAdapter.java
@@ -155,8 +155,6 @@ public abstract class GridCacheQueryFutureAdapter<K, V, R> extends GridFutureAda
     @Override public boolean onDone(Collection<R> res, Throwable err) {
         cctx.time().removeTimeoutObject(this);
 
-        qry.query().onCompleted(res, err, startTime(), duration());
-
         return super.onDone(res, err);
     }
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java
index 00a9181..ab8bd63 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryManager.java
@@ -26,6 +26,7 @@ import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -46,6 +47,7 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.IgniteException;
 import org.apache.ignite.cache.query.QueryMetrics;
 import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.configuration.CacheConfiguration;
 import org.apache.ignite.events.CacheQueryExecutedEvent;
 import org.apache.ignite.events.CacheQueryReadEvent;
 import org.apache.ignite.events.DiscoveryEvent;
@@ -80,6 +82,7 @@ import org.apache.ignite.internal.processors.query.GridQueryIndexType;
 import org.apache.ignite.internal.processors.query.GridQueryProcessor;
 import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
 import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.util.GridBoundedPriorityQueue;
 import org.apache.ignite.internal.util.GridCloseableIteratorAdapter;
 import org.apache.ignite.internal.util.GridEmptyCloseableIterator;
 import org.apache.ignite.internal.util.GridEmptyIterator;
@@ -134,8 +137,37 @@ import static org.apache.ignite.internal.processors.cache.query.GridCacheQueryTy
  */
 @SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
 public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapter<K, V> {
+    /** Maximum number of query detail metrics to evict at once. */
+    private static final int QRY_DETAIL_METRICS_EVICTION_LIMIT = 10_000;
+
+    /** Comparator for priority queue with query detail metrics with priority to new metrics. */
+    private static final Comparator<GridCacheQueryDetailMetricsAdapter> QRY_DETAIL_METRICS_PRIORITY_NEW_CMP =
+        new Comparator<GridCacheQueryDetailMetricsAdapter>() {
+            @Override public int compare(GridCacheQueryDetailMetricsAdapter m1, GridCacheQueryDetailMetricsAdapter m2) {
+                return Long.compare(m1.lastStartTime(), m2.lastStartTime());
+            }
+        };
+
+    /** Comparator for priority queue with query detail metrics with priority to old metrics. */
+    private static final Comparator<GridCacheQueryDetailMetricsAdapter> QRY_DETAIL_METRICS_PRIORITY_OLD_CMP =
+        new Comparator<GridCacheQueryDetailMetricsAdapter>() {
+            @Override public int compare(GridCacheQueryDetailMetricsAdapter m1, GridCacheQueryDetailMetricsAdapter m2) {
+                return Long.compare(m2.lastStartTime(), m1.lastStartTime());
+            }
+        };
+
+    /** Function to merge query detail metrics. */
+    private static final ConcurrentHashMap8.BiFun QRY_DETAIL_METRICS_MERGE_FX =
+        new ConcurrentHashMap8.BiFun<GridCacheQueryDetailMetricsAdapter,
+            GridCacheQueryDetailMetricsAdapter, GridCacheQueryDetailMetricsAdapter>() {
+            @Override public GridCacheQueryDetailMetricsAdapter apply(GridCacheQueryDetailMetricsAdapter oldVal,
+                GridCacheQueryDetailMetricsAdapter newVal) {
+                return oldVal.aggregate(newVal);
+            }
+        };
+
     /** */
-    protected GridQueryProcessor qryProc;
+    private GridQueryProcessor qryProc;
 
     /** */
     private String space;
@@ -147,8 +179,13 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
     private volatile GridCacheQueryMetricsAdapter metrics = new GridCacheQueryMetricsAdapter();
 
     /** */
-    private final ConcurrentMap<UUID, RequestFutureMap> qryIters =
-        new ConcurrentHashMap8<>();
+    private int detailMetricsSz;
+
+    /** */
+    private ConcurrentHashMap8<GridCacheQueryDetailMetricsKey, GridCacheQueryDetailMetricsAdapter> detailMetrics;
+
+    /** */
+    private final ConcurrentMap<UUID, RequestFutureMap> qryIters = new ConcurrentHashMap8<>();
 
     /** */
     private final ConcurrentMap<UUID, Map<Long, GridFutureAdapter<FieldsResult>>> fieldsQryRes =
@@ -171,10 +208,18 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
 
     /** {@inheritDoc} */
     @Override public void start0() throws IgniteCheckedException {
+        CacheConfiguration ccfg = cctx.config();
+
         qryProc = cctx.kernalContext().query();
         space = cctx.name();
-        maxIterCnt = cctx.config().getMaxQueryIteratorsCount();
 
+        maxIterCnt = ccfg.getMaxQueryIteratorsCount();
+
+        detailMetricsSz = ccfg.getQueryDetailMetricsSize();
+
+        if (detailMetricsSz > 0)
+            detailMetrics = new ConcurrentHashMap8<>(detailMetricsSz);
+        
         lsnr = new GridLocalEventListener() {
             @Override public void onEvent(Event evt) {
                 UUID nodeId = ((DiscoveryEvent)evt).eventNode().id();
@@ -213,7 +258,7 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
 
         cctx.events().addListener(lsnr, EVT_NODE_LEFT, EVT_NODE_FAILED);
 
-        enabled = GridQueryProcessor.isEnabled(cctx.config());
+        enabled = GridQueryProcessor.isEnabled(ccfg);
 
         qryTopVer = cctx.startTopologyVersion();
 
@@ -1691,6 +1736,8 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
 
         long startTime = U.currentTimeMillis();
 
+        final String namex = cctx.namex();
+
         try {
             assert qry.type() == SCAN;
 
@@ -1699,7 +1746,6 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
 
             final String taskName = cctx.kernalContext().task().resolveTaskName(qry.taskHash());
             final IgniteBiPredicate filter = qry.scanFilter();
-            final String namex = cctx.namex();
             final ClusterNode locNode = cctx.localNode();
             final UUID subjId = qry.subjectId();
 
@@ -1721,12 +1767,9 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
 
             final GridCloseableIterator<IgniteBiTuple<K, V>> iter = scanIterator(qry, true);
 
-            if (updStatisticsIfNeeded) {
+            if (updStatisticsIfNeeded)
                 needUpdStatistics = false;
 
-                cctx.queries().onCompleted(U.currentTimeMillis() - startTime, false);
-            }
-
             final boolean readEvt = cctx.gridEvents().isRecordable(EVT_CACHE_QUERY_OBJECT_READ);
 
             return new GridCloseableIteratorAdapter<Object>() {
@@ -1789,7 +1832,8 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
         }
         catch (Exception e) {
             if (needUpdStatistics)
-                cctx.queries().onCompleted(U.currentTimeMillis() - startTime, true);
+                cctx.queries().collectMetrics(GridCacheQueryType.SCAN, namex, startTime,
+                    U.currentTimeMillis() - startTime, true);
 
             throw e;
         }
@@ -2071,6 +2115,52 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
     }
 
     /**
+     * Gets cache queries detailed metrics.
+     * Detail metrics could be enabled by setting non-zero value via {@link CacheConfiguration#setQueryDetailMetricsSize(int)}
+     *
+     * @return Cache queries metrics aggregated by query type and query text.
+     */
+    public Collection<GridCacheQueryDetailMetricsAdapter> detailMetrics() {
+        if (detailMetricsSz > 0) {
+            // Return no more than latest detailMetricsSz items.
+            if (detailMetrics.size() > detailMetricsSz) {
+                GridBoundedPriorityQueue<GridCacheQueryDetailMetricsAdapter> latestMetrics =
+                    new GridBoundedPriorityQueue<>(detailMetricsSz, QRY_DETAIL_METRICS_PRIORITY_NEW_CMP);
+
+                latestMetrics.addAll(detailMetrics.values());
+
+                return latestMetrics;
+            }
+
+            return new ArrayList<>(detailMetrics.values());
+        }
+
+        return Collections.emptyList();
+    }
+
+    /**
+     * Evict detail metrics.
+     */
+    public void evictDetailMetrics() {
+        if (detailMetricsSz > 0) {
+            int sz = detailMetrics.size();
+
+            if (sz > detailMetricsSz) {
+                // Limit number of metrics to evict in order make eviction time predictable.
+                int evictCnt = Math.min(QRY_DETAIL_METRICS_EVICTION_LIMIT, sz - detailMetricsSz);
+
+                Queue<GridCacheQueryDetailMetricsAdapter> metricsToEvict =
+                    new GridBoundedPriorityQueue<>(evictCnt, QRY_DETAIL_METRICS_PRIORITY_OLD_CMP);
+
+                metricsToEvict.addAll(detailMetrics.values());
+
+                for (GridCacheQueryDetailMetricsAdapter m : metricsToEvict)
+                    detailMetrics.remove(m.key());
+            }
+        }
+    }
+
+    /**
      * Resets metrics.
      */
     public void resetMetrics() {
@@ -2078,18 +2168,43 @@ public abstract class GridCacheQueryManager<K, V> extends GridCacheManagerAdapte
     }
 
     /**
-     * @param fail {@code true} if execution failed.
+     * Resets detail metrics.
      */
-    public void onExecuted(boolean fail) {
-        metrics.onQueryExecute(fail);
+    public void resetDetailMetrics() {
+        if (detailMetrics != null)
+            detailMetrics.clear();
     }
 
     /**
+     * @param qryType Query type.
+     * @param qry Query description.
+     * @param startTime Query start size.
      * @param duration Execution duration.
-     * @param fail {@code true} if execution failed.
+     * @param failed {@code True} if query execution failed.
      */
-    public void onCompleted(long duration, boolean fail) {
-        metrics.onQueryCompleted(duration, fail);
+    public void collectMetrics(GridCacheQueryType qryType, String qry, long startTime, long duration, boolean failed) {
+        metrics.update(duration, failed);
+
+        if (detailMetricsSz > 0) {
+            // Do not collect metrics for EXPLAIN queries.
+            if (qryType == SQL_FIELDS && !F.isEmpty(qry)) {
+                int off = 0;
+                int len = qry.length();
+
+                while (off < len && Character.isWhitespace(qry.charAt(off)))
+                    off++;
+
+                if (qry.regionMatches(true, off, "EXPLAIN", 0, 7))
+                    return;
+            }
+
+            GridCacheQueryDetailMetricsAdapter m = new GridCacheQueryDetailMetricsAdapter(qryType, qry,
+                cctx.name(), startTime, duration, failed);
+
+            GridCacheQueryDetailMetricsKey key = m.key();
+
+            detailMetrics.merge(key, m, QRY_DETAIL_METRICS_MERGE_FX);
+        }
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryMetricsAdapter.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryMetricsAdapter.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryMetricsAdapter.java
index e70ea9f..0bb578f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryMetricsAdapter.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/query/GridCacheQueryMetricsAdapter.java
@@ -100,32 +100,23 @@ public class GridCacheQueryMetricsAdapter implements QueryMetrics, Externalizabl
     }
 
     /**
-     * Callback for query execution.
-     *
-     * @param fail {@code True} query executed unsuccessfully {@code false} otherwise.
-     */
-    public void onQueryExecute(boolean fail) {
-        execs.increment();
-
-        if (fail)
-            fails.increment();
-    }
-
-    /**
-     * Callback for completion of query execution.
+     * Update metrics.
      *
      * @param duration Duration of queue execution.
      * @param fail {@code True} query executed unsuccessfully {@code false} otherwise.
      */
-    public void onQueryCompleted(long duration, boolean fail) {
-        minTime.setIfLess(duration);
-        maxTime.setIfGreater(duration);
-
-        if (fail)
+    public void update(long duration, boolean fail) {
+        if (fail) {
+            execs.increment();
             fails.increment();
+        }
         else {
+            execs.increment();
             completed.increment();
 
+            minTime.setIfLess(duration);
+            maxTime.setIfGreater(duration);
+
             sumTime.add(duration);
         }
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
index 27c0b71..8befa0e 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
@@ -70,10 +70,13 @@ import org.apache.ignite.internal.processors.cache.CacheObject;
 import org.apache.ignite.internal.processors.cache.CacheObjectContext;
 import org.apache.ignite.internal.processors.cache.GridCacheContext;
 import org.apache.ignite.internal.processors.cache.GridCacheDefaultAffinityKeyMapper;
+import org.apache.ignite.internal.processors.cache.IgniteCacheProxy;
 import org.apache.ignite.internal.processors.cache.QueryCursorImpl;
 import org.apache.ignite.internal.processors.cache.binary.CacheObjectBinaryProcessorImpl;
 import org.apache.ignite.internal.processors.cache.query.CacheQueryFuture;
 import org.apache.ignite.internal.processors.cache.query.CacheQueryType;
+import org.apache.ignite.internal.processors.cache.query.GridCacheQueryType;
+import org.apache.ignite.internal.processors.timeout.GridTimeoutProcessor;
 import org.apache.ignite.internal.util.GridSpinBusyLock;
 import org.apache.ignite.internal.util.future.GridCompoundFuture;
 import org.apache.ignite.internal.util.future.GridFinishedFuture;
@@ -111,6 +114,9 @@ public class GridQueryProcessor extends GridProcessorAdapter {
     /** */
     private static final Class<?> GEOMETRY_CLASS = U.classForName("com.vividsolutions.jts.geom.Geometry", null);
 
+    /** Queries detail metrics eviction frequency. */
+    private static final int QRY_DETAIL_METRICS_EVICTION_FREQ = 3_000;
+
     /** */
     private static Set<Class<?>> SQL_TYPES = new HashSet<>(F.<Class<?>>asList(
         Integer.class,
@@ -149,6 +155,9 @@ public class GridQueryProcessor extends GridProcessorAdapter {
     private final GridQueryIndexing idx;
 
     /** */
+    private GridTimeoutProcessor.CancelableTask qryDetailMetricsEvictTask;
+
+    /** */
     private static final ThreadLocal<AffinityTopologyVersion> requestTopVer = new ThreadLocal<>();
 
     /**
@@ -177,6 +186,14 @@ public class GridQueryProcessor extends GridProcessorAdapter {
 
             idx.start(ctx, busyLock);
         }
+
+        // Schedule queries detail metrics eviction.
+        qryDetailMetricsEvictTask = ctx.timeout().schedule(new Runnable() {
+            @Override public void run() {
+                for (IgniteCacheProxy cache : ctx.cache().jcaches())
+                    cache.context().queries().evictDetailMetrics();
+            }
+        }, QRY_DETAIL_METRICS_EVICTION_FREQ, QRY_DETAIL_METRICS_EVICTION_FREQ);
     }
 
     /**
@@ -478,6 +495,8 @@ public class GridQueryProcessor extends GridProcessorAdapter {
 
         if (idx != null)
             idx.stop();
+
+        U.closeQuiet(qryDetailMetricsEvictTask);
     }
 
     /** {@inheritDoc} */
@@ -754,7 +773,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
         try {
             final GridCacheContext<?, ?> cctx = ctx.cache().internalCache(space).context();
 
-            return executeQuery(cctx, new IgniteOutClosureX<GridCloseableIterator<IgniteBiTuple<K, V>>>() {
+            return executeQuery(GridCacheQueryType.SQL_FIELDS, clause, cctx, new IgniteOutClosureX<GridCloseableIterator<IgniteBiTuple<K, V>>>() {
                 @Override public GridCloseableIterator<IgniteBiTuple<K, V>> applyx() throws IgniteCheckedException {
                     TypeDescriptor type = typesByName.get(new TypeName(space, resType));
 
@@ -782,7 +801,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
             throw new IllegalStateException("Failed to execute query (grid is stopping).");
 
         try {
-            return executeQuery(cctx, new IgniteOutClosureX<QueryCursor<List<?>>>() {
+            return executeQuery(GridCacheQueryType.SQL_FIELDS, qry.getSql(), cctx, new IgniteOutClosureX<QueryCursor<List<?>>>() {
                 @Override public QueryCursor<List<?>> applyx() {
                     return idx.queryTwoStep(cctx, qry);
                 }
@@ -808,11 +827,11 @@ public class GridQueryProcessor extends GridProcessorAdapter {
             throw new IllegalStateException("Failed to execute query (grid is stopping).");
 
         try {
-            return executeQuery(cctx, new IgniteOutClosureX<QueryCursor<Cache.Entry<K, V>>>() {
+            return executeQuery(GridCacheQueryType.SQL, qry.getSql(), cctx, new IgniteOutClosureX<QueryCursor<Cache.Entry<K, V>>>() {
                 @Override public QueryCursor<Cache.Entry<K, V>> applyx() throws IgniteCheckedException {
                     return idx.queryTwoStep(cctx, qry);
                 }
-            }, false);
+            }, true);
         }
         catch (IgniteCheckedException e) {
             throw new IgniteException(e);
@@ -837,8 +856,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
             throw new IllegalStateException("Failed to execute query (grid is stopping).");
 
         try {
-            return executeQuery(
-                cctx,
+            return executeQuery(GridCacheQueryType.SQL, qry.getSql(), cctx,
                 new IgniteOutClosureX<Iterator<Cache.Entry<K, V>>>() {
                     @Override public Iterator<Cache.Entry<K, V>> applyx() throws IgniteCheckedException {
                         String space = cctx.name();
@@ -887,7 +905,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
                             }
                         };
                     }
-                }, false);
+                }, true);
         }
         catch (IgniteCheckedException e) {
             throw new IgniteException(e);
@@ -956,7 +974,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
         try {
             final boolean keepBinary = cctx.keepBinary();
 
-            return executeQuery(cctx, new IgniteOutClosureX<QueryCursor<List<?>>>() {
+            return executeQuery(GridCacheQueryType.SQL_FIELDS, qry.getSql(), cctx, new IgniteOutClosureX<QueryCursor<List<?>>>() {
                 @Override public QueryCursor<List<?>> applyx() throws IgniteCheckedException {
                     final String space = cctx.name();
                     final String sql = qry.getSql();
@@ -1115,20 +1133,21 @@ public class GridQueryProcessor extends GridProcessorAdapter {
         try {
             final GridCacheContext<?, ?> cctx = ctx.cache().internalCache(space).context();
 
-            return executeQuery(cctx, new IgniteOutClosureX<GridCloseableIterator<IgniteBiTuple<K, V>>>() {
-                @Override public GridCloseableIterator<IgniteBiTuple<K, V>> applyx() throws IgniteCheckedException {
-                    TypeDescriptor type = typesByName.get(new TypeName(space, resType));
+            return executeQuery(GridCacheQueryType.TEXT, clause, cctx,
+                new IgniteOutClosureX<GridCloseableIterator<IgniteBiTuple<K, V>>>() {
+                    @Override public GridCloseableIterator<IgniteBiTuple<K, V>> applyx() throws IgniteCheckedException {
+                        TypeDescriptor type = typesByName.get(new TypeName(space, resType));
 
-                    if (type == null || !type.registered())
-                        throw new CacheException("Failed to find SQL table for type: " + resType);
+                        if (type == null || !type.registered())
+                            throw new CacheException("Failed to find SQL table for type: " + resType);
 
-                    return idx.queryLocalText(
-                        space,
-                        clause,
-                        type,
-                        filters);
-                }
-            }, false);
+                        return idx.queryLocalText(
+                            space,
+                            clause,
+                            type,
+                            filters);
+                    }
+                }, true);
         }
         finally {
             busyLock.leaveBusy();
@@ -1744,11 +1763,13 @@ public class GridQueryProcessor extends GridProcessorAdapter {
     }
 
     /**
+     * @param qryType Query type.
+     * @param qry Query description.
      * @param cctx Cache context.
      * @param clo Closure.
      * @param complete Complete.
      */
-    public <R> R executeQuery(GridCacheContext<?, ?> cctx, IgniteOutClosureX<R> clo, boolean complete)
+    public <R> R executeQuery(GridCacheQueryType qryType, String qry, GridCacheContext<?, ?> cctx, IgniteOutClosureX<R> clo, boolean complete)
         throws IgniteCheckedException {
         final long startTime = U.currentTimeMillis();
 
@@ -1783,30 +1804,18 @@ public class GridQueryProcessor extends GridProcessorAdapter {
             throw new IgniteCheckedException(e);
         }
         finally {
-            cctx.queries().onExecuted(err != null);
-
-            if (complete && err == null)
-                onCompleted(cctx, res, null, startTime, U.currentTimeMillis() - startTime, log);
-        }
-    }
+            boolean failed = err != null;
 
-    /**
-     * @param cctx Cctx.
-     * @param res Result.
-     * @param err Err.
-     * @param startTime Start time.
-     * @param duration Duration.
-     * @param log Logger.
-     */
-    public static void onCompleted(GridCacheContext<?, ?> cctx, Object res, Throwable err,
-        long startTime, long duration, IgniteLogger log) {
-        boolean fail = err != null;
+            long duration = U.currentTimeMillis() - startTime;
 
-        cctx.queries().onCompleted(duration, fail);
+            if (complete || failed) {
+                cctx.queries().collectMetrics(qryType, qry, startTime, duration, failed);
 
-        if (log.isTraceEnabled())
-            log.trace("Query execution completed [startTime=" + startTime +
-                ", duration=" + duration + ", fail=" + fail + ", res=" + res + ']');
+                if (log.isTraceEnabled())
+                    log.trace("Query execution [startTime=" + startTime + ", duration=" + duration +
+                        ", fail=" + failed + ", res=" + res + ']');
+            }
+        }
     }
 
     /**
@@ -2485,6 +2494,11 @@ public class GridQueryProcessor extends GridProcessorAdapter {
      * The way to index.
      */
     private enum IndexType {
-        ASC, DESC, TEXT
+        /** Ascending index. */
+        ASC,
+        /** Descending index. */
+        DESC,
+        /** Text index. */
+        TEXT
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/util/GridBoundedPriorityQueue.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/GridBoundedPriorityQueue.java b/modules/core/src/main/java/org/apache/ignite/internal/util/GridBoundedPriorityQueue.java
new file mode 100644
index 0000000..cbc521d
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/GridBoundedPriorityQueue.java
@@ -0,0 +1,68 @@
+/*
+ * 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.util;
+
+import java.util.Comparator;
+import java.util.PriorityQueue;
+import org.apache.ignite.internal.util.typedef.internal.A;
+
+/**
+ * Bounded variant of {@link PriorityQueue}.
+ *
+ * @param <E> Type of the queue element.
+ */
+public class GridBoundedPriorityQueue<E> extends PriorityQueue<E> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Queue max capacity. */
+    private final int maxCap;
+
+    /** Comparator. */
+    private final Comparator<? super E> cmp;
+
+    /**
+     * Creates a bounded priority queue with the specified maximum size.
+     * At most {@code maxCap} elements would be kept in the queue.
+     *
+     * @param maxCap Maximum size of the queue.
+     * @param cmp Comparator that orders the elements.
+     */
+    public GridBoundedPriorityQueue(int maxCap, Comparator<? super E> cmp) {
+        super(maxCap, cmp);
+
+        A.notNull(cmp, "comparator not null");
+
+        this.maxCap = maxCap;
+        this.cmp = cmp;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean offer(E e) {
+        if (size() >= maxCap) {
+            E head = peek();
+
+            if (cmp.compare(e, head) <= 0)
+                return false;
+
+            poll();
+        }
+
+        return super.offer(e);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryDetailMetrics.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryDetailMetrics.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryDetailMetrics.java
new file mode 100644
index 0000000..dc46906
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryDetailMetrics.java
@@ -0,0 +1,167 @@
+/*
+ * 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.visor.cache;
+
+import java.io.Serializable;
+import org.apache.ignite.cache.query.QueryDetailMetrics;
+import org.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * Data transfer object for cache query detail metrics.
+ */
+public class VisorCacheQueryDetailMetrics implements Serializable {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Query type. */
+    private String qryType;
+
+    /** Textual query representation. */
+    private String qry;
+
+    /** Cache name. */
+    private String cache;
+
+    /** Number of executions. */
+    private int execs;
+
+    /** Number of completions executions. */
+    private int completions;
+
+    /** Number of failures. */
+    private int failures;
+
+    /** Minimum time of execution. */
+    private long minTime;
+
+    /** Maximum time of execution. */
+    private long maxTime;
+
+    /** Average time of execution. */
+    private double avgTime;
+
+    /** Sum of execution time of completions time. */
+    private long totalTime;
+
+    /** Sum of execution time of completions time. */
+    private long lastStartTime;
+
+    /**
+     * @param m Cache query metrics.
+     * @return Data transfer object for given cache metrics.
+     */
+    public VisorCacheQueryDetailMetrics from(QueryDetailMetrics m) {
+        qryType = m.queryType();
+        qry = m.query();
+        cache = m.cache();
+
+        execs = m.executions();
+        completions = m.completions();
+        failures = m.failures();
+
+        minTime = m.minimumTime();
+        maxTime = m.maximumTime();
+        avgTime = m.averageTime();
+        totalTime = m.totalTime();
+        lastStartTime = m.lastStartTime();
+
+        return this;
+    }
+
+    /**
+     * @return Query type
+     */
+    public String getQueryType() {
+        return qryType;
+    }
+
+    /**
+     * @return Query type
+     */
+    public String getQuery() {
+        return qry;
+    }
+
+    /**
+     * @return Cache name where query was executed.
+     */
+    public String getCache() {
+        return cache;
+    }
+
+    /**
+     * @return Number of executions.
+     */
+    public int getExecutions() {
+        return execs;
+    }
+
+    /**
+     * @return Number of completed executions.
+     */
+    public int getCompletions() {
+        return completions;
+    }
+
+    /**
+     * @return Total number of times a query execution failed.
+     */
+    public int getFailures() {
+        return failures;
+    }
+
+    /**
+     * @return Minimum execution time of query.
+     */
+    public long getMinimumTime() {
+        return minTime;
+    }
+
+    /**
+     * @return Maximum execution time of query.
+     */
+    public long getMaximumTime() {
+        return maxTime;
+    }
+
+    /**
+     * @return Average execution time of query.
+     */
+    public double getAverageTime() {
+        return avgTime;
+    }
+
+    /**
+     * @return Total time of all query executions.
+     */
+    public long getTotalTime() {
+        return totalTime;
+    }
+
+    /**
+     * @return Latest time query was stared.
+     */
+    public long getLastStartTime() {
+        return lastStartTime;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(VisorCacheQueryDetailMetrics.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryDetailMetricsCollectorTask.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryDetailMetricsCollectorTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryDetailMetricsCollectorTask.java
new file mode 100644
index 0000000..aee5063
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheQueryDetailMetricsCollectorTask.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.visor.cache;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.cache.query.QueryDetailMetrics;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
+import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
+import org.apache.ignite.internal.processors.cache.query.GridCacheQueryDetailMetricsAdapter;
+import org.apache.ignite.internal.processors.cache.query.GridCacheQueryDetailMetricsKey;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorMultiNodeTask;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.processors.cache.GridCacheUtils.isIgfsCache;
+import static org.apache.ignite.internal.processors.cache.GridCacheUtils.isSystemCache;
+
+/**
+ * Task to collect cache query metrics.
+ */
+@GridInternal
+public class VisorCacheQueryDetailMetricsCollectorTask extends VisorMultiNodeTask<Long, Collection<VisorCacheQueryDetailMetrics>,
+    Collection<? extends QueryDetailMetrics>> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** {@inheritDoc} */
+    @Override protected VisorCacheQueryDetailMetricsCollectorJob job(Long arg) {
+        return new VisorCacheQueryDetailMetricsCollectorJob(arg, debug);
+    }
+
+    /** {@inheritDoc} */
+    @Nullable @Override protected Collection<VisorCacheQueryDetailMetrics> reduce0(List<ComputeJobResult> results)
+        throws IgniteException {
+        Map<GridCacheQueryDetailMetricsKey, GridCacheQueryDetailMetricsAdapter> taskRes = new HashMap<>();
+
+        for (ComputeJobResult res : results) {
+            if (res.getException() != null)
+                throw res.getException();
+
+            Collection<GridCacheQueryDetailMetricsAdapter> metrics = res.getData();
+
+            VisorCacheQueryDetailMetricsCollectorJob.aggregateMetrics(-1, taskRes, metrics);
+        }
+
+        Collection<GridCacheQueryDetailMetricsAdapter> aggMetrics = taskRes.values();
+
+        Collection<VisorCacheQueryDetailMetrics> res = new ArrayList<>(aggMetrics.size());
+
+        for (GridCacheQueryDetailMetricsAdapter m : aggMetrics)
+            res.add(new VisorCacheQueryDetailMetrics().from(m));
+
+        return res;
+    }
+
+    /**
+     * Job that will actually collect query metrics.
+     */
+    private static class VisorCacheQueryDetailMetricsCollectorJob extends VisorJob<Long, Collection<? extends QueryDetailMetrics>> {
+        /** */
+        private static final long serialVersionUID = 0L;
+
+        /**
+         * Create job with specified argument.
+         *
+         * @param arg Last time when metrics were collected.
+         * @param debug Debug flag.
+         */
+        protected VisorCacheQueryDetailMetricsCollectorJob(@Nullable Long arg, boolean debug) {
+            super(arg, debug);
+        }
+
+        /**
+         * @param since Time when metrics were collected last time.
+         * @param res Response.
+         * @param metrics Metrics.
+         */
+        private static void aggregateMetrics(long since, Map<GridCacheQueryDetailMetricsKey,
+            GridCacheQueryDetailMetricsAdapter> res, Collection<GridCacheQueryDetailMetricsAdapter> metrics) {
+            for (GridCacheQueryDetailMetricsAdapter m : metrics) {
+                if (m.lastStartTime() > since) {
+                    GridCacheQueryDetailMetricsKey key = m.key();
+
+                    GridCacheQueryDetailMetricsAdapter aggMetrics = res.get(key);
+
+                    res.put(key, aggMetrics == null ? m : aggMetrics.aggregate(m));
+                }
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override protected Collection<? extends QueryDetailMetrics> run(@Nullable Long arg) throws IgniteException {
+            assert arg != null;
+
+            IgniteConfiguration cfg = ignite.configuration();
+
+            GridCacheProcessor cacheProc = ignite.context().cache();
+
+            Collection<String> cacheNames = cacheProc.cacheNames();
+
+            Map<GridCacheQueryDetailMetricsKey, GridCacheQueryDetailMetricsAdapter> aggMetrics = new HashMap<>();
+
+            for (String cacheName : cacheNames) {
+                if (!isSystemCache(cacheName) && !isIgfsCache(cfg, cacheName)) {
+                    IgniteInternalCache<Object, Object> cache = cacheProc.cache(cacheName);
+
+                    if (cache == null || !cache.context().started())
+                        continue;
+
+                    aggregateMetrics(arg, aggMetrics, cache.context().queries().detailMetrics());
+                }
+            }
+
+            return new ArrayList<>(aggMetrics.values());
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(VisorCacheQueryDetailMetricsCollectorJob.class, this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheResetQueryDetailMetricsTask.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheResetQueryDetailMetricsTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheResetQueryDetailMetricsTask.java
new file mode 100644
index 0000000..84aae9d
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheResetQueryDetailMetricsTask.java
@@ -0,0 +1,71 @@
+/*
+ * 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.visor.cache;
+
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorOneNodeTask;
+
+/**
+ * Reset query detail metrics.
+ */
+@GridInternal
+public class VisorCacheResetQueryDetailMetricsTask extends VisorOneNodeTask<Void, Void> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** {@inheritDoc} */
+    @Override protected VisorCacheResetQueryDetailMetricsJob job(Void arg) {
+        return new VisorCacheResetQueryDetailMetricsJob(arg, debug);
+    }
+
+    /**
+     * Job that reset query detail metrics.
+     */
+    private static class VisorCacheResetQueryDetailMetricsJob extends VisorJob<Void, Void> {
+        /** */
+        private static final long serialVersionUID = 0L;
+
+        /**
+         * @param arg Task argument.
+         * @param debug Debug flag.
+         */
+        private VisorCacheResetQueryDetailMetricsJob(Void arg, boolean debug) {
+            super(arg, debug);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected Void run(Void arg) {
+            for (String cacheName : ignite.cacheNames()) {
+                IgniteCache cache = ignite.cache(cacheName);
+
+                if (cache != null)
+                    cache.resetQueryDetailMetrics();
+            }
+
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(VisorCacheResetQueryDetailMetricsJob.class, this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheResetQueryMetricsTask.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheResetQueryMetricsTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheResetQueryMetricsTask.java
index 96d9857..5ba6156 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheResetQueryMetricsTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/VisorCacheResetQueryMetricsTask.java
@@ -17,7 +17,7 @@
 
 package org.apache.ignite.internal.visor.cache;
 
-import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
+import org.apache.ignite.IgniteCache;
 import org.apache.ignite.internal.processors.task.GridInternal;
 import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.visor.VisorJob;
@@ -53,10 +53,10 @@ public class VisorCacheResetQueryMetricsTask extends VisorOneNodeTask<String, Vo
 
         /** {@inheritDoc} */
         @Override protected Void run(String cacheName) {
-            IgniteInternalCache cache = ignite.cachex(cacheName);
+            IgniteCache cache = ignite.cache(cacheName);
 
             if (cache != null)
-                cache.context().queries().resetMetrics();
+                cache.resetQueryMetrics();
 
             return null;
         }
@@ -66,4 +66,4 @@ public class VisorCacheResetQueryMetricsTask extends VisorOneNodeTask<String, Vo
             return S.toString(VisorCacheResetQueryMetricsJob.class, this);
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/0234f673/modules/core/src/main/java/org/apache/ignite/internal/visor/util/VisorTaskUtils.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/util/VisorTaskUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/util/VisorTaskUtils.java
index 25aaab5..1e9346c 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/util/VisorTaskUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/util/VisorTaskUtils.java
@@ -158,8 +158,8 @@ public class VisorTaskUtils {
      * @param name Grid-style nullable name.
      * @return Name with {@code null} replaced to &lt;default&gt;.
      */
-    public static String escapeName(@Nullable String name) {
-        return name == null ? DFLT_EMPTY_NAME : name;
+    public static String escapeName(@Nullable Object name) {
+        return name == null ? DFLT_EMPTY_NAME : name.toString();
     }
 
     /**
@@ -173,15 +173,6 @@ public class VisorTaskUtils {
     }
 
     /**
-     * @param a First name.
-     * @param b Second name.
-     * @return {@code true} if both names equals.
-     */
-    public static boolean safeEquals(@Nullable String a, @Nullable String b) {
-        return (a != null && b != null) ? a.equals(b) : (a == null && b == null);
-    }
-
-    /**
      * Concat arrays in one.
      *
      * @param arrays Arrays.
@@ -637,12 +628,10 @@ public class VisorTaskUtils {
             else {
                 int toRead = Math.min(blockSz, (int)(fSz - pos));
 
-                byte[] buf = new byte[toRead];
-
                 raf = new RandomAccessFile(file, "r");
-
                 raf.seek(pos);
 
+                byte[] buf = new byte[toRead];
                 int cntRead = raf.read(buf, 0, toRead);
 
                 if (cntRead != toRead)
@@ -887,8 +876,6 @@ public class VisorTaskUtils {
         try {
             for (int i = 0; i < nodesToStart; i++) {
                 if (U.isMacOs()) {
-                    StringBuilder envs = new StringBuilder();
-
                     Map<String, String> macEnv = new HashMap<>(System.getenv());
 
                     if (envVars != null) {
@@ -905,6 +892,8 @@ public class VisorTaskUtils {
                                 macEnv.put(ent.getKey(), ent.getValue());
                     }
 
+                    StringBuilder envs = new StringBuilder();
+
                     for (Map.Entry<String, String> entry : macEnv.entrySet()) {
                         String val = entry.getValue();
 
@@ -1024,13 +1013,12 @@ public class VisorTaskUtils {
         ByteArrayOutputStream bos = new ByteArrayOutputStream(initBufSize);
 
         try (ZipOutputStream zos = new ZipOutputStream(bos)) {
-            ZipEntry entry = new ZipEntry("");
-
             try {
+                ZipEntry entry = new ZipEntry("");
+
                 entry.setSize(input.length);
 
                 zos.putNextEntry(entry);
-
                 zos.write(input);
             }
             finally {