You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ag...@apache.org on 2017/04/14 15:27:55 UTC

[30/44] ignite git commit: IGNITE-4988 Cleanup and refactor VisorXxx tasks and DTO for ignite-2.0

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryArg.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryArg.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryArg.java
index 00587b4..1cb1f0d 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryArg.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryArg.java
@@ -17,32 +17,44 @@
 
 package org.apache.ignite.internal.visor.query;
 
-import java.io.Serializable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.visor.VisorDataTransferObject;
 
 /**
  * Arguments for {@link VisorQueryTask}.
  */
-public class VisorQueryArg implements Serializable {
+public class VisorQueryArg extends VisorDataTransferObject {
     /** */
     private static final long serialVersionUID = 0L;
 
     /** Cache name for query. */
-    private final String cacheName;
+    private String cacheName;
 
     /** Query text. */
-    private final String qryTxt;
+    private String qryTxt;
 
     /** Distributed joins enabled flag. */
-    private final boolean distributedJoins;
+    private boolean distributedJoins;
 
     /** Enforce join order flag. */
-    private final boolean enforceJoinOrder;
+    private boolean enforceJoinOrder;
 
     /** Flag whether to execute query locally. */
-    private final boolean loc;
+    private boolean loc;
 
     /** Result batch size. */
-    private final int pageSize;
+    private int pageSize;
+
+    /**
+     * Default constructor.
+     */
+    public VisorQueryArg() {
+        // No-op.
+    }
 
     /**
      * @param cacheName Cache name for query.
@@ -65,42 +77,67 @@ public class VisorQueryArg implements Serializable {
     /**
      * @return Cache name.
      */
-    public String cacheName() {
+    public String getCacheName() {
         return cacheName;
     }
 
     /**
      * @return Query txt.
      */
-    public String queryText() {
+    public String getQueryText() {
         return qryTxt;
     }
 
     /**
      * @return Distributed joins enabled flag.
      */
-    public boolean distributedJoins() {
+    public boolean isDistributedJoins() {
         return distributedJoins;
     }
 
     /**
      * @return Enforce join order flag.
      */
-    public boolean enforceJoinOrder() {
+    public boolean isEnforceJoinOrder() {
         return enforceJoinOrder;
     }
 
     /**
      * @return {@code true} if query should be executed locally.
      */
-    public boolean local() {
+    public boolean isLocal() {
         return loc;
     }
 
     /**
      * @return Page size.
      */
-    public int pageSize() {
+    public int getPageSize() {
         return pageSize;
     }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeString(out, cacheName);
+        U.writeString(out, qryTxt);
+        out.writeBoolean(distributedJoins);
+        out.writeBoolean(enforceJoinOrder);
+        out.writeBoolean(loc);
+        out.writeInt(pageSize);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        cacheName = U.readString(in);
+        qryTxt = U.readString(in);
+        distributedJoins = in.readBoolean();
+        enforceJoinOrder = in.readBoolean();
+        loc = in.readBoolean();
+        pageSize = in.readInt();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(VisorQueryArg.class, this);
+    }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCancelTask.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCancelTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCancelTask.java
new file mode 100644
index 0000000..6b81dc4
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCancelTask.java
@@ -0,0 +1,72 @@
+/*
+ * 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.query;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.compute.ComputeJobResult;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorOneNodeTask;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Task to cancel queries.
+ */
+@GridInternal
+public class VisorQueryCancelTask extends VisorOneNodeTask<Long, Void> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** {@inheritDoc} */
+    @Override protected VisorCancelQueriesJob job(Long arg) {
+        return new VisorCancelQueriesJob(arg, debug);
+    }
+
+    /** {@inheritDoc} */
+    @Nullable @Override protected Void reduce0(List<ComputeJobResult> results) throws IgniteException {
+        return null;
+    }
+
+    /**
+     * Job to cancel queries on node.
+     */
+    private static class VisorCancelQueriesJob extends VisorJob<Long, Void> {
+        /** */
+        private static final long serialVersionUID = 0L;
+
+        /**
+         * Create job with specified argument.
+         *
+         * @param arg Job argument.
+         * @param debug Flag indicating whether debug information should be printed into node log.
+         */
+        protected VisorCancelQueriesJob(@Nullable Long arg, boolean debug) {
+            super(arg, debug);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected Void run(@Nullable Long queries) throws IgniteException {
+            ignite.context().query().cancelQueries(Collections.singleton(queries));
+
+            return null;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCleanupTask.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCleanupTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCleanupTask.java
index c1f06ae..572cf3b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCleanupTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryCleanupTask.java
@@ -65,10 +65,10 @@ public class VisorQueryCleanupTask extends VisorMultiNodeTask<Map<UUID, Collecti
                     map.put(new VisorQueryCleanupJob(taskArg.get(node.id()), debug), node);
 
             if (map.isEmpty()) {
-                String notFoundNodes = "";
+                StringBuilder notFoundNodes = new StringBuilder();
 
                 for (UUID nid : nodeIds)
-                    notFoundNodes = notFoundNodes + (notFoundNodes.isEmpty() ? "" : ",")  + U.id8(nid);
+                    notFoundNodes.append((notFoundNodes.length() == 0) ? "" : ",").append(U.id8(nid));
 
                 throw new VisorClusterGroupEmptyException("Failed to clear query results. Nodes are not available: [" +
                     notFoundNodes + "]");
@@ -123,4 +123,4 @@ public class VisorQueryCleanupTask extends VisorMultiNodeTask<Map<UUID, Collecti
             return S.toString(VisorQueryCleanupJob.class, this);
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryConfiguration.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryConfiguration.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryConfiguration.java
new file mode 100644
index 0000000..92921b2
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryConfiguration.java
@@ -0,0 +1,142 @@
+/*
+ * 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.query;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.List;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.visor.VisorDataTransferObject;
+
+import static org.apache.ignite.internal.visor.util.VisorTaskUtils.compactClasses;
+
+/**
+ * Data transfer object for cache query configuration data.
+ */
+public class VisorQueryConfiguration extends VisorDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** */
+    private List<String> sqlFuncClss;
+
+    /** */
+    private long longQryWarnTimeout;
+
+    /** */
+    private boolean sqlEscapeAll;
+
+    /** */
+    private List<String> indexedTypes;
+
+    /** */
+    private int sqlOnheapRowCacheSize;
+
+    /** */
+    private String sqlSchema;
+
+    /**
+     * Default constructor.
+     */
+    public VisorQueryConfiguration() {
+        // No-op.
+    }
+
+    /**
+     * Create data transfer object with cache query configuration data.
+     *
+     * @param ccfg Cache configuration.
+     */
+    public VisorQueryConfiguration(CacheConfiguration ccfg) {
+        sqlFuncClss = compactClasses(ccfg.getSqlFunctionClasses());
+        longQryWarnTimeout = ccfg.getLongQueryWarningTimeout();
+        sqlEscapeAll = ccfg.isSqlEscapeAll();
+        indexedTypes = compactClasses(ccfg.getIndexedTypes());
+        sqlSchema = ccfg.getSqlSchema();
+    }
+
+    /**
+     * @return Classes names with SQL functions.
+     */
+    public List<String> getSqlFunctionClasses() {
+        return sqlFuncClss;
+    }
+
+    /**
+     * @return Timeout in milliseconds after which long query warning will be printed.
+     */
+    public long getLongQueryWarningTimeout() {
+        return longQryWarnTimeout;
+    }
+
+    /**
+     * @return {@code true} if SQL engine generate SQL statements with escaped names.
+     */
+    public boolean isSqlEscapeAll() {
+        return sqlEscapeAll;
+    }
+
+    /**
+     * @return Array of key and value classes names to be indexed.
+     */
+    public List<String> getIndexedTypes() {
+        return indexedTypes;
+    }
+
+    /**
+     * @return Number of SQL rows which will be cached onheap to avoid deserialization on each SQL index access.
+     */
+    public int getSqlOnheapRowCacheSize() {
+        return sqlOnheapRowCacheSize;
+    }
+
+    /**
+     * @return Schema name, which is used by SQL engine for SQL statements generation.
+     */
+    public String getSqlSchema() {
+        return sqlSchema;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeCollection(out, sqlFuncClss);
+        out.writeLong(longQryWarnTimeout);
+        out.writeBoolean(sqlEscapeAll);
+        U.writeCollection(out, indexedTypes);
+        out.writeInt(sqlOnheapRowCacheSize);
+        U.writeString(out, sqlSchema);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        sqlFuncClss = U.readList(in);
+        longQryWarnTimeout = in.readLong();
+        sqlEscapeAll = in.readBoolean();
+        indexedTypes = U.readList(in);
+        sqlOnheapRowCacheSize = in.readInt();
+        sqlSchema = U.readString(in);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(VisorQueryConfiguration.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryDetailMetrics.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryDetailMetrics.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryDetailMetrics.java
new file mode 100644
index 0000000..b747845
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryDetailMetrics.java
@@ -0,0 +1,205 @@
+/*
+ * 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.query;
+
+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;
+import org.apache.ignite.internal.visor.VisorDataTransferObject;
+
+/**
+ * Data transfer object for cache query detail metrics.
+ */
+public class VisorQueryDetailMetrics extends VisorDataTransferObject {
+    /** */
+    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;
+
+    /**
+     * Default constructor
+     */
+    public VisorQueryDetailMetrics() {
+        // No-op.
+    }
+
+    /**
+     * @param m Cache query metrics.
+     */
+    public VisorQueryDetailMetrics(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 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 protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeString(out, qryType);
+        U.writeString(out, qry);
+        U.writeString(out, cache);
+        out.writeInt(execs);
+        out.writeInt(completions);
+        out.writeInt(failures);
+        out.writeLong(minTime);
+        out.writeLong(maxTime);
+        out.writeDouble(avgTime);
+        out.writeLong(totalTime);
+        out.writeLong(lastStartTime);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        qryType = U.readString(in);
+        qry = U.readString(in);
+        cache = U.readString(in);
+        execs = in.readInt();
+        completions = in.readInt();
+        failures = in.readInt();
+        minTime = in.readLong();
+        maxTime = in.readLong();
+        avgTime = in.readDouble();
+        totalTime = in.readLong();
+        lastStartTime = in.readLong();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(VisorQueryDetailMetrics.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryDetailMetricsCollectorTask.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryDetailMetricsCollectorTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryDetailMetricsCollectorTask.java
new file mode 100644
index 0000000..7c1379f
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryDetailMetricsCollectorTask.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.query;
+
+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 VisorQueryDetailMetricsCollectorTask extends VisorMultiNodeTask<Long, Collection<VisorQueryDetailMetrics>,
+    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<VisorQueryDetailMetrics> 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<VisorQueryDetailMetrics> res = new ArrayList<>(aggMetrics.size());
+
+        for (GridCacheQueryDetailMetricsAdapter m: aggMetrics)
+            res.add(new VisorQueryDetailMetrics(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/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryEntity.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryEntity.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryEntity.java
new file mode 100644
index 0000000..9f4dfe7
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryEntity.java
@@ -0,0 +1,188 @@
+/*
+ * 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.query;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.QueryIndex;
+import org.apache.ignite.internal.processors.igfs.IgfsUtils;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.visor.VisorDataTransferObject;
+
+/**
+ * Data transfer object for {@link QueryEntity}.
+ */
+public class VisorQueryEntity extends VisorDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Key class used to store key in cache. */
+    private String keyType;
+
+    /** Value class used to store value in cache. */
+    private String valType;
+
+    /** Fields to be queried, in addition to indexed fields. */
+    private Map<String, String> qryFlds;
+
+    /** Key fields. */
+    private List<String> keyFields;
+
+    /** Aliases. */
+    private Map<String, String> aliases;
+
+    /** Table name. */
+    private String tblName;
+
+    /** Fields to create group indexes for. */
+    private List<VisorQueryIndex> grps;
+
+    /**
+     * @param qryEntities Collection of query entities.
+     * @return Data transfer object for query entities.
+     */
+    public static List<VisorQueryEntity> list(Collection<QueryEntity> qryEntities) {
+        List<VisorQueryEntity> entities = new ArrayList<>();
+
+        // Add query entries.
+        if (!F.isEmpty(qryEntities))
+            for (QueryEntity qryEntity : qryEntities)
+                entities.add(new VisorQueryEntity(qryEntity));
+
+        return entities;
+    }
+
+    /**
+     * Create data transfer object for given cache type metadata.
+     */
+    public VisorQueryEntity() {
+        // No-op.
+    }
+
+    /**
+     * Create data transfer object for given cache type metadata.
+     *
+     * @param q Actual cache query entities.
+     */
+    private VisorQueryEntity(QueryEntity q) {
+        assert q != null;
+
+        keyType = q.getKeyType();
+        valType = q.getValueType();
+
+        keyFields = toList(q.getKeyFields());
+
+        LinkedHashMap<String, String> qryFields = q.getFields();
+
+        qryFlds = new LinkedHashMap<>(qryFields);
+
+        aliases = U.copyMap(q.getAliases());
+
+        Collection<QueryIndex> qryIdxs = q.getIndexes();
+
+        grps = new ArrayList<>(qryIdxs.size());
+
+        for (QueryIndex qryIdx : qryIdxs)
+            grps.add(new VisorQueryIndex(qryIdx));
+    }
+
+    /**
+     * @return Key class used to store key in cache.
+     */
+    public String getKeyType() {
+        return keyType;
+    }
+
+    /**
+     * @return Value class used to store value in cache.
+     */
+    public String getValueType() {
+        return valType;
+    }
+
+    /**
+     * @return Key fields.
+     */
+    public List<String> getKeyFields() {
+        return keyFields;
+    }
+
+    /**
+     * @return Fields to be queried, in addition to indexed fields.
+     */
+    public Map<String, String> getQueryFields() {
+        return qryFlds;
+    }
+
+    /**
+     * @return Field aliases.
+     */
+    public Map<String, String> getAliases() {
+        return aliases;
+    }
+
+    /**
+     * @return Table name.
+     */
+    public String getTableName() {
+        return tblName;
+    }
+
+    /**
+     * @return Fields to create group indexes for.
+     */
+    public List<VisorQueryIndex> getGroups() {
+        return grps;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeString(out, keyType);
+        U.writeString(out, valType);
+        U.writeCollection(out, keyFields);
+        IgfsUtils.writeStringMap(out, qryFlds);
+        U.writeMap(out, aliases);
+        U.writeString(out, tblName);
+        U.writeCollection(out, grps);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        keyType = U.readString(in);
+        valType = U.readString(in);
+        keyFields = U.readList(in);
+        qryFlds = IgfsUtils.readStringMap(in);
+        aliases = U.readMap(in);
+        tblName = U.readString(in);
+        grps = U.readList(in);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(VisorQueryEntity.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryField.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryField.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryField.java
index 18b0d71..ad84dda 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryField.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryField.java
@@ -17,15 +17,18 @@
 
 package org.apache.ignite.internal.visor.query;
 
-import java.io.Serializable;
-import org.apache.ignite.internal.LessNamingBean;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
 import org.apache.ignite.internal.util.typedef.F;
 import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.visor.VisorDataTransferObject;
 
 /**
  * Data transfer object for query field type description.
  */
-public class VisorQueryField implements Serializable, LessNamingBean {
+public class VisorQueryField extends VisorDataTransferObject {
     /** */
     private static final long serialVersionUID = 0L;
 
@@ -42,6 +45,13 @@ public class VisorQueryField implements Serializable, LessNamingBean {
     private String fieldTypeName;
 
     /**
+     * Default constructor.
+     */
+    public VisorQueryField() {
+        // No-op.
+    }
+
+    /**
      * Create data transfer object with given parameters.
      *
      * @param schemaName Schema name.
@@ -59,28 +69,28 @@ public class VisorQueryField implements Serializable, LessNamingBean {
     /**
      * @return Schema name.
      */
-    public String schemaName() {
+    public String getSchemaName() {
         return schemaName;
     }
 
     /**
      * @return Type name.
      */
-    public String typeName() {
+    public String getTypeName() {
         return typeName;
     }
 
     /**
      * @return Field name.
      */
-    public String fieldName() {
+    public String getFieldName() {
         return fieldName;
     }
 
     /**
      * @return Field type name.
      */
-    public String fieldTypeName() {
+    public String getFieldTypeName() {
         return fieldTypeName;
     }
 
@@ -88,7 +98,7 @@ public class VisorQueryField implements Serializable, LessNamingBean {
      * @param schema If {@code true} then add schema name to full name.
      * @return Fully qualified field name with type name and schema name.
      */
-    public String fullName(boolean schema) {
+    public String getFullName(boolean schema) {
         if (!F.isEmpty(typeName)) {
             if (schema && !F.isEmpty(schemaName))
                 return schemaName + "." + typeName + "." + fieldName;
@@ -100,6 +110,22 @@ public class VisorQueryField implements Serializable, LessNamingBean {
     }
 
     /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeString(out, schemaName);
+        U.writeString(out, typeName);
+        U.writeString(out, fieldName);
+        U.writeString(out, fieldTypeName);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        schemaName = U.readString(in);
+        typeName = U.readString(in);
+        fieldName = U.readString(in);
+        fieldTypeName = U.readString(in);
+    }
+
+    /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(VisorQueryField.class, this);
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryIndex.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryIndex.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryIndex.java
new file mode 100644
index 0000000..d9fa2a4
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryIndex.java
@@ -0,0 +1,105 @@
+/*
+ * 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.query;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.List;
+import org.apache.ignite.cache.QueryIndex;
+import org.apache.ignite.cache.QueryIndexType;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.visor.VisorDataTransferObject;
+
+/**
+ * Data transfer object for {@link QueryIndex}.
+ */
+public class VisorQueryIndex extends VisorDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Name of index. */
+    private String name;
+
+    /** Type of index. */
+    private QueryIndexType type;
+
+    /** Fields to create group indexes for. */
+    private List<VisorQueryIndexField> fields;
+
+    /**
+     * Create data transfer object for given cache type metadata.
+     */
+    public VisorQueryIndex() {
+        // No-op.
+    }
+
+    /**
+     * Create data transfer object for given cache type metadata.
+     *
+     * @param idx Actual cache query entity index.
+     */
+    public VisorQueryIndex(QueryIndex idx) {
+        assert idx != null;
+
+        name = idx.getName();
+        type = idx.getIndexType();
+        fields = VisorQueryIndexField.list(idx);
+    }
+
+    /**
+     * @return Name of index.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @return Type of index.
+     */
+    public QueryIndexType getType() {
+        return type;
+    }
+
+    /**
+     * @return Fields to create group indexes for.
+     */
+    public List<VisorQueryIndexField> getFields() {
+        return fields;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeString(out, name);
+        U.writeEnum(out, type);
+        U.writeCollection(out, fields);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        name = U.readString(in);
+        type = QueryIndexType.fromOrdinal(in.readByte());
+        fields = U.readList(in);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(VisorQueryIndex.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryIndexField.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryIndexField.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryIndexField.java
new file mode 100644
index 0000000..fb32dd1
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryIndexField.java
@@ -0,0 +1,106 @@
+/*
+ * 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.query;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.apache.ignite.cache.QueryEntity;
+import org.apache.ignite.cache.QueryIndex;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.visor.VisorDataTransferObject;
+
+/**
+ * Data transfer object for {@link QueryEntity}.
+ */
+public class VisorQueryIndexField extends VisorDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Index field name. */
+    private String fldName;
+
+    /** Index field sort order. */
+    private boolean fldSort;
+
+    /**
+     * Create data transfer object for given cache type metadata.
+     */
+    public VisorQueryIndexField() {
+        // No-op.
+    }
+
+    /**
+     * Create data transfer object for given cache type metadata.
+     *
+     * @param fldName Index field name.
+     * @param fldSort Index field sort order.
+     */
+    public VisorQueryIndexField(String fldName, boolean fldSort) {
+        this.fldName = fldName;
+        this.fldSort = fldSort;
+    }
+
+    /**
+     * @param idx Query entity index.
+     * @return Data transfer object for query entity index fields.
+     */
+    public static List<VisorQueryIndexField> list(QueryIndex idx) {
+        List<VisorQueryIndexField> res = new ArrayList<>();
+
+        for (Map.Entry<String, Boolean> field: idx.getFields().entrySet())
+            res.add(new VisorQueryIndexField(field.getKey(), !field.getValue()));
+
+        return res;
+    }
+
+    /**
+     * @return Index field name.
+     */
+    public String getFieldName() {
+        return fldName;
+    }
+
+    /**
+     * @return Index field sort order.
+     */
+    public boolean isFieldSort() {
+        return fldSort;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeString(out, fldName);
+        out.writeBoolean(fldSort);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        fldName = U.readString(in);
+        fldSort = in.readBoolean();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(VisorQueryIndexField.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryJob.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryJob.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryJob.java
deleted file mode 100644
index 61ccac2..0000000
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryJob.java
+++ /dev/null
@@ -1,275 +0,0 @@
-/*
- * 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.query;
-
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.ConcurrentMap;
-import javax.cache.Cache;
-import org.apache.ignite.IgniteCache;
-import org.apache.ignite.cache.CachePeekMode;
-import org.apache.ignite.cache.query.QueryCursor;
-import org.apache.ignite.cache.query.ScanQuery;
-import org.apache.ignite.cache.query.SqlFieldsQuery;
-import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
-import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata;
-import org.apache.ignite.internal.processors.timeout.GridTimeoutObjectAdapter;
-import org.apache.ignite.internal.util.typedef.internal.S;
-import org.apache.ignite.internal.util.typedef.internal.U;
-import org.apache.ignite.internal.visor.VisorJob;
-import org.apache.ignite.internal.visor.util.VisorExceptionWrapper;
-import org.apache.ignite.lang.IgniteBiPredicate;
-import org.apache.ignite.lang.IgniteBiTuple;
-
-import static org.apache.ignite.internal.visor.query.VisorQueryUtils.RMV_DELAY;
-import static org.apache.ignite.internal.visor.query.VisorQueryUtils.SCAN_CACHE_WITH_FILTER;
-import static org.apache.ignite.internal.visor.query.VisorQueryUtils.SCAN_CACHE_WITH_FILTER_CASE_SENSITIVE;
-import static org.apache.ignite.internal.visor.query.VisorQueryUtils.SCAN_COL_NAMES;
-import static org.apache.ignite.internal.visor.query.VisorQueryUtils.SCAN_QRY_NAME;
-import static org.apache.ignite.internal.visor.query.VisorQueryUtils.SCAN_NEAR_CACHE;
-import static org.apache.ignite.internal.visor.query.VisorQueryUtils.SQL_QRY_NAME;
-import static org.apache.ignite.internal.visor.query.VisorQueryUtils.fetchScanQueryRows;
-import static org.apache.ignite.internal.visor.query.VisorQueryUtils.fetchSqlQueryRows;
-
-/**
- * Job for execute SCAN or SQL query and get first page of results.
- */
-public class VisorQueryJob extends VisorJob<VisorQueryArg, IgniteBiTuple<? extends VisorExceptionWrapper, VisorQueryResultEx>> {
-    /** */
-    private static final long serialVersionUID = 0L;
-
-    /**
-     * Create job with specified argument.
-     *
-     * @param arg Job argument.
-     * @param debug Debug flag.
-     */
-    protected VisorQueryJob(VisorQueryArg arg, boolean debug) {
-        super(arg, debug);
-    }
-
-    /**
-     * @param cacheName Cache name.
-     * @return Cache to execute query.
-     */
-    protected IgniteCache<Object, Object> cache(String cacheName) {
-        GridCacheProcessor cacheProcessor = ignite.context().cache();
-
-        return cacheProcessor.jcache(cacheName);
-    }
-
-    /**
-     * Execute scan query.
-     *
-     * @param c Cache to scan.
-     * @param arg Job argument with query parameters.
-     * @return Query cursor.
-     */
-    private QueryCursor<Cache.Entry<Object, Object>> scan(IgniteCache<Object, Object> c, VisorQueryArg arg,
-        IgniteBiPredicate<Object, Object> filter) {
-        ScanQuery<Object, Object> qry = new ScanQuery<>(filter);
-        qry.setPageSize(arg.pageSize());
-        qry.setLocal(arg.local());
-
-        return c.withKeepBinary().query(qry);
-    }
-
-    /**
-     * Scan near cache.
-     *
-     * @param c Cache to scan near entries.
-     * @return Cache entries iterator wrapped with query cursor.
-     */
-    private QueryCursor<Cache.Entry<Object, Object>> near(IgniteCache<Object, Object> c) {
-        return new VisorNearCacheCursor<>(c.localEntries(CachePeekMode.NEAR).iterator());
-    }
-
-    /** {@inheritDoc} */
-    @Override protected IgniteBiTuple<? extends VisorExceptionWrapper, VisorQueryResultEx> run(final VisorQueryArg arg) {
-        try {
-            UUID nid = ignite.localNode().id();
-
-            String qryTxt = arg.queryText();
-
-            boolean scan = qryTxt == null;
-
-            boolean scanWithFilter = qryTxt != null && qryTxt.startsWith(SCAN_CACHE_WITH_FILTER);
-
-            boolean near = qryTxt != null && qryTxt.startsWith(SCAN_NEAR_CACHE);
-
-            boolean scanAny = scan || scanWithFilter || near;
-
-            // Generate query ID to store query cursor in node local storage.
-            String qryId = (scanAny ? SCAN_QRY_NAME : SQL_QRY_NAME) + "-" + UUID.randomUUID();
-
-            IgniteCache<Object, Object> c = cache(arg.cacheName());
-
-            if (scanAny) {
-                long start = U.currentTimeMillis();
-
-                IgniteBiPredicate<Object, Object> filter = null;
-
-                if (scanWithFilter) {
-                    boolean caseSensitive = qryTxt.startsWith(SCAN_CACHE_WITH_FILTER_CASE_SENSITIVE);
-
-                    String ptrn =  qryTxt.substring(caseSensitive
-                        ? SCAN_CACHE_WITH_FILTER_CASE_SENSITIVE.length()
-                        : SCAN_CACHE_WITH_FILTER.length());
-
-                    filter = new VisorQueryScanSubstringFilter(caseSensitive, ptrn);
-                }
-
-                VisorQueryCursor<Cache.Entry<Object, Object>> cur = new VisorQueryCursor<>(near ? near(c) : scan(c, arg, filter));
-
-                List<Object[]> rows = fetchScanQueryRows(cur, arg.pageSize());
-
-                long duration = U.currentTimeMillis() - start; // Scan duration + fetch duration.
-
-                boolean hasNext = cur.hasNext();
-
-                if (hasNext) {
-                    ignite.cluster().<String, VisorQueryCursor>nodeLocalMap().put(qryId, cur);
-
-                    scheduleResultSetHolderRemoval(qryId);
-                }
-                else
-                    cur.close();
-
-                return new IgniteBiTuple<>(null, new VisorQueryResultEx(nid, qryId, SCAN_COL_NAMES, rows, hasNext,
-                    duration));
-            }
-            else {
-                SqlFieldsQuery qry = new SqlFieldsQuery(arg.queryText());
-                qry.setPageSize(arg.pageSize());
-                qry.setDistributedJoins(arg.distributedJoins());
-                qry.setEnforceJoinOrder(arg.enforceJoinOrder());
-                qry.setLocal(arg.local());
-
-                long start = U.currentTimeMillis();
-
-                VisorQueryCursor<List<?>> cur = new VisorQueryCursor<>(c.withKeepBinary().query(qry));
-
-                Collection<GridQueryFieldMetadata> meta = cur.fieldsMeta();
-
-                if (meta == null)
-                    return new IgniteBiTuple<>(
-                        new VisorExceptionWrapper(new SQLException("Fail to execute query. No metadata available.")), null);
-                else {
-                    List<VisorQueryField> names = new ArrayList<>(meta.size());
-
-                    for (GridQueryFieldMetadata col : meta)
-                        names.add(new VisorQueryField(col.schemaName(), col.typeName(),
-                            col.fieldName(), col.fieldTypeName()));
-
-                    List<Object[]> rows = fetchSqlQueryRows(cur, arg.pageSize());
-
-                    long duration = U.currentTimeMillis() - start; // Query duration + fetch duration.
-
-                    boolean hasNext = cur.hasNext();
-
-                    if (hasNext) {
-                        ignite.cluster().<String, VisorQueryCursor<List<?>>>nodeLocalMap().put(qryId, cur);
-
-                        scheduleResultSetHolderRemoval(qryId);
-                    }
-                    else
-                        cur.close();
-
-                    return new IgniteBiTuple<>(null, new VisorQueryResultEx(nid, qryId, names, rows, hasNext, duration));
-                }
-            }
-        }
-        catch (Throwable e) {
-            return new IgniteBiTuple<>(new VisorExceptionWrapper(e), null);
-        }
-    }
-
-    /**
-     * @param qryId Unique query result id.
-     */
-    private void scheduleResultSetHolderRemoval(final String qryId) {
-        ignite.context().timeout().addTimeoutObject(new GridTimeoutObjectAdapter(RMV_DELAY) {
-            @Override public void onTimeout() {
-                ConcurrentMap<String, VisorQueryCursor> storage = ignite.cluster().nodeLocalMap();
-
-                VisorQueryCursor cur = storage.get(qryId);
-
-                if (cur != null) {
-                    // If cursor was accessed since last scheduling, set access flag to false and reschedule.
-                    if (cur.accessed()) {
-                        cur.accessed(false);
-
-                        scheduleResultSetHolderRemoval(qryId);
-                    }
-                    else {
-                        // Remove stored cursor otherwise.
-                        storage.remove(qryId);
-
-                        cur.close();
-                    }
-                }
-            }
-        });
-    }
-
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return S.toString(VisorQueryJob.class, this);
-    }
-
-    /**
-     * Wrapper for cache iterator to behave like {@link QueryCursor}.
-     */
-    private static class VisorNearCacheCursor<T> implements QueryCursor<T> {
-        /** Wrapped iterator.  */
-        private final Iterator<T> it;
-
-        /**
-         * Wrapping constructor.
-         *
-         * @param it Near cache iterator to wrap.
-         */
-        private VisorNearCacheCursor(Iterator<T> it) {
-            this.it = it;
-        }
-
-        /** {@inheritDoc} */
-        @Override public List<T> getAll() {
-            List<T> all = new ArrayList<>();
-
-            while(it.hasNext())
-                all.add(it.next());
-
-            return all;
-        }
-
-        /** {@inheritDoc} */
-        @Override public void close() {
-            // Nothing to close.
-        }
-
-        /** {@inheritDoc} */
-        @Override public Iterator<T> iterator() {
-            return it;
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryMetrics.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryMetrics.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryMetrics.java
new file mode 100644
index 0000000..f878ab6
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryMetrics.java
@@ -0,0 +1,125 @@
+/*
+ * 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.query;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import org.apache.ignite.cache.query.QueryMetrics;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.visor.VisorDataTransferObject;
+
+/**
+ * Data transfer object for cache query metrics.
+ */
+public class VisorQueryMetrics extends VisorDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Minimum execution time of query. */
+    private long minTime;
+
+    /** Maximum execution time of query. */
+    private long maxTime;
+
+    /** Average execution time of query. */
+    private double avgTime;
+
+    /** Number of executions. */
+    private int execs;
+
+    /** Total number of times a query execution failed. */
+    private int fails;
+
+    /**
+     * Default constructor.
+     */
+    public VisorQueryMetrics() {
+        // No-op.
+    }
+
+    /**
+     * Create data transfer object for given cache metrics.
+     * @param m Cache query metrics.
+     */
+    public VisorQueryMetrics(QueryMetrics m) {
+        minTime = m.minimumTime();
+        maxTime = m.maximumTime();
+        avgTime = m.averageTime();
+        execs = m.executions();
+        fails = m.fails();
+    }
+
+    /**
+     * @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 Number of executions.
+     */
+    public int getExecutions() {
+        return execs;
+    }
+
+    /**
+     * @return Total number of times a query execution failed.
+     */
+    public int getFailures() {
+        return fails;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        out.writeLong(minTime);
+        out.writeLong(maxTime);
+        out.writeDouble(avgTime);
+        out.writeInt(execs);
+        out.writeInt(fails);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        minTime = in.readLong();
+        maxTime = in.readLong();
+        avgTime = in.readDouble();
+        execs = in.readInt();
+        fails = in.readInt();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(VisorQueryMetrics.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryNextPageTask.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryNextPageTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryNextPageTask.java
index 52a167d..4684c49 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryNextPageTask.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryNextPageTask.java
@@ -26,25 +26,24 @@ import org.apache.ignite.internal.util.typedef.internal.S;
 import org.apache.ignite.internal.util.typedef.internal.U;
 import org.apache.ignite.internal.visor.VisorJob;
 import org.apache.ignite.internal.visor.VisorOneNodeTask;
-import org.apache.ignite.lang.IgniteBiTuple;
 
 /**
  * Task for collecting next page previously executed SQL or SCAN query.
  */
 @GridInternal
-public class VisorQueryNextPageTask extends VisorOneNodeTask<IgniteBiTuple<String, Integer>, VisorQueryResult> {
+public class VisorQueryNextPageTask extends VisorOneNodeTask<VisorQueryNextPageTaskArg, VisorQueryResult> {
     /** */
     private static final long serialVersionUID = 0L;
 
     /** {@inheritDoc} */
-    @Override protected VisorQueryNextPageJob job(IgniteBiTuple<String, Integer> arg) {
+    @Override protected VisorQueryNextPageJob job(VisorQueryNextPageTaskArg arg) {
         return new VisorQueryNextPageJob(arg, debug);
     }
 
     /**
      * Job for collecting next page previously executed SQL or SCAN query.
      */
-    private static class VisorQueryNextPageJob extends VisorJob<IgniteBiTuple<String, Integer>, VisorQueryResult> {
+    private static class VisorQueryNextPageJob extends VisorJob<VisorQueryNextPageTaskArg, VisorQueryResult> {
         /** */
         private static final long serialVersionUID = 0L;
 
@@ -54,13 +53,13 @@ public class VisorQueryNextPageTask extends VisorOneNodeTask<IgniteBiTuple<Strin
          * @param arg Job argument.
          * @param debug Debug flag.
          */
-        private VisorQueryNextPageJob(IgniteBiTuple<String, Integer> arg, boolean debug) {
+        private VisorQueryNextPageJob(VisorQueryNextPageTaskArg arg, boolean debug) {
             super(arg, debug);
         }
 
         /** {@inheritDoc} */
-        @Override protected VisorQueryResult run(IgniteBiTuple<String, Integer> arg) {
-            return arg.get1().startsWith(VisorQueryUtils.SCAN_QRY_NAME) ? nextScanPage(arg) : nextSqlPage(arg);
+        @Override protected VisorQueryResult run(VisorQueryNextPageTaskArg arg) {
+            return arg.getQueryId().startsWith(VisorQueryUtils.SCAN_QRY_NAME) ? nextScanPage(arg) : nextSqlPage(arg);
         }
 
         /**
@@ -69,19 +68,19 @@ public class VisorQueryNextPageTask extends VisorOneNodeTask<IgniteBiTuple<Strin
          * @param arg Query name and page size.
          * @return Query result with next page.
          */
-        private VisorQueryResult nextSqlPage(IgniteBiTuple<String, Integer> arg) {
+        private VisorQueryResult nextSqlPage(VisorQueryNextPageTaskArg arg) {
             long start = U.currentTimeMillis();
 
             ConcurrentMap<String, VisorQueryCursor<List<?>>> storage = ignite.cluster().nodeLocalMap();
 
-            String qryId = arg.get1();
+            String qryId = arg.getQueryId();
 
             VisorQueryCursor<List<?>> cur = storage.get(qryId);
 
             if (cur == null)
                 throw new IgniteException("SQL query results are expired.");
 
-            List<Object[]> nextRows = VisorQueryUtils.fetchSqlQueryRows(cur, arg.get2());
+            List<Object[]> nextRows = VisorQueryUtils.fetchSqlQueryRows(cur, arg.getPageSize());
 
             boolean hasMore = cur.hasNext();
 
@@ -93,7 +92,8 @@ public class VisorQueryNextPageTask extends VisorOneNodeTask<IgniteBiTuple<Strin
                 cur.close();
             }
 
-            return new VisorQueryResult(nextRows, hasMore, U.currentTimeMillis() - start);
+            return new VisorQueryResult(ignite.localNode().id(), qryId, null, nextRows, hasMore,
+                U.currentTimeMillis() - start);
         }
 
         /**
@@ -102,19 +102,19 @@ public class VisorQueryNextPageTask extends VisorOneNodeTask<IgniteBiTuple<Strin
          * @param arg Query name and page size.
          * @return Next page with data.
          */
-        private VisorQueryResult nextScanPage(IgniteBiTuple<String, Integer> arg) {
+        private VisorQueryResult nextScanPage(VisorQueryNextPageTaskArg arg) {
             long start = U.currentTimeMillis();
 
             ConcurrentMap<String, VisorQueryCursor<Cache.Entry<Object, Object>>> storage = ignite.cluster().nodeLocalMap();
 
-            String qryId = arg.get1();
+            String qryId = arg.getQueryId();
 
             VisorQueryCursor<Cache.Entry<Object, Object>> cur = storage.get(qryId);
 
             if (cur == null)
                 throw new IgniteException("Scan query results are expired.");
 
-            List<Object[]> rows = VisorQueryUtils.fetchScanQueryRows(cur, arg.get2());
+            List<Object[]> rows = VisorQueryUtils.fetchScanQueryRows(cur, arg.getPageSize());
 
             boolean hasMore = cur.hasNext();
 
@@ -126,7 +126,8 @@ public class VisorQueryNextPageTask extends VisorOneNodeTask<IgniteBiTuple<Strin
                 cur.close();
             }
 
-            return new VisorQueryResult(rows, hasMore, U.currentTimeMillis() - start);
+            return new VisorQueryResult(ignite.localNode().id(), qryId, null, rows, hasMore,
+                U.currentTimeMillis() - start);
         }
 
         /** {@inheritDoc} */
@@ -134,4 +135,4 @@ public class VisorQueryNextPageTask extends VisorOneNodeTask<IgniteBiTuple<Strin
             return S.toString(VisorQueryNextPageJob.class, this);
         }
     }
-}
\ No newline at end of file
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryNextPageTaskArg.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryNextPageTaskArg.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryNextPageTaskArg.java
new file mode 100644
index 0000000..d0f62b9
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryNextPageTaskArg.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.visor.query;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.visor.VisorDataTransferObject;
+
+/**
+ * Arguments for {@link VisorQueryNextPageTask}.
+ */
+public class VisorQueryNextPageTaskArg extends VisorDataTransferObject {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** ID of query execution. */
+    private String qryId;
+
+    /** Number of rows to load. */
+    private int pageSize;
+
+    /**
+     * Default constructor.
+     */
+    public VisorQueryNextPageTaskArg() {
+        // No-op.
+    }
+
+    /**
+     * @param qryId ID of query execution.
+     * @param pageSize Number of rows to load.
+     */
+    public VisorQueryNextPageTaskArg(String qryId, int pageSize) {
+        this.qryId = qryId;
+        this.pageSize = pageSize;
+    }
+
+    /**
+     * @return ID of query execution.
+     */
+    public String getQueryId() {
+        return qryId;
+    }
+
+    /**
+     * @return Number of rows to load.
+     */
+    public int getPageSize() {
+        return pageSize;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeString(out, qryId);
+        out.writeInt(pageSize);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        qryId = U.readString(in);
+        pageSize = in.readInt();
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(VisorQueryNextPageTaskArg.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResetDetailMetricsTask.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResetDetailMetricsTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResetDetailMetricsTask.java
new file mode 100644
index 0000000..6d35e32
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResetDetailMetricsTask.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.query;
+
+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 VisorQueryResetDetailMetricsTask 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/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResetMetricsTask.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResetMetricsTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResetMetricsTask.java
new file mode 100644
index 0000000..3c5c668
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResetMetricsTask.java
@@ -0,0 +1,69 @@
+/*
+ * 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.query;
+
+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 compute grid query metrics.
+ */
+@GridInternal
+public class VisorQueryResetMetricsTask extends VisorOneNodeTask<String, Void> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** {@inheritDoc} */
+    @Override protected VisorQueryResetMetricsJob job(String arg) {
+        return new VisorQueryResetMetricsJob(arg, debug);
+    }
+
+    /**
+     * Job that reset cache query metrics.
+     */
+    private static class VisorQueryResetMetricsJob extends VisorJob<String, Void> {
+        /** */
+        private static final long serialVersionUID = 0L;
+
+        /**
+         * @param arg Cache name to reset query metrics for.
+         * @param debug Debug flag.
+         */
+        private VisorQueryResetMetricsJob(String arg, boolean debug) {
+            super(arg, debug);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected Void run(String cacheName) {
+            IgniteCache cache = ignite.cache(cacheName);
+
+            if (cache != null)
+                cache.resetQueryMetrics();
+
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        @Override public String toString() {
+            return S.toString(VisorQueryResetMetricsJob.class, this);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResult.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResult.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResult.java
index 21d1ed7..f7beae2 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResult.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResult.java
@@ -17,62 +17,135 @@
 
 package org.apache.ignite.internal.visor.query;
 
-import java.io.Serializable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Collection;
 import java.util.List;
-import org.apache.ignite.internal.LessNamingBean;
+import java.util.UUID;
 import org.apache.ignite.internal.util.typedef.internal.S;
+import org.apache.ignite.internal.util.typedef.internal.U;
+import org.apache.ignite.internal.visor.VisorDataTransferObject;
 
 /**
  * Result for cache query tasks.
  */
-public class VisorQueryResult implements Serializable, LessNamingBean {
+public class VisorQueryResult extends VisorDataTransferObject {
     /** */
     private static final long serialVersionUID = 0L;
 
+    /** Node where query executed. */
+    private UUID resNodeId;
+
+    /** Query ID to store in node local. */
+    private String qryId;
+
+    /** Query columns descriptors. */
+    private List<VisorQueryField> cols;
+
     /** Rows fetched from query. */
-    private final List<Object[]> rows;
+    private List<Object[]> rows;
 
     /** Whether query has more rows to fetch. */
-    private final boolean hasMore;
+    private boolean hasMore;
 
     /** Query duration */
-    private final long duration;
+    private long duration;
+
+    /**
+     * Default constructor.
+     */
+    public VisorQueryResult() {
+        // No-op.
+    }
 
     /**
-     * Create task result with given parameters
-     *
+     * @param resNodeId Node where query executed.
+     * @param qryId Query ID for future extraction in nextPage() access.
+     * @param cols Columns descriptors.
      * @param rows Rows fetched from query.
      * @param hasMore Whether query has more rows to fetch.
      * @param duration Query duration.
      */
-    public VisorQueryResult(List<Object[]> rows, boolean hasMore, long duration) {
+    public VisorQueryResult(
+        UUID resNodeId,
+        String qryId,
+        List<VisorQueryField> cols,
+        List<Object[]> rows,
+        boolean hasMore,
+        long duration
+    ) {
+        this.resNodeId = resNodeId;
+        this.qryId = qryId;
+        this.cols = cols;
         this.rows = rows;
         this.hasMore = hasMore;
         this.duration = duration;
     }
 
     /**
+     * @return Response node id.
+     */
+    public UUID getResponseNodeId() {
+        return resNodeId;
+    }
+
+    /**
+     * @return Query id.
+     */
+    public String getQueryId() {
+        return qryId;
+    }
+
+    /**
+     * @return Columns.
+     */
+    public Collection<VisorQueryField> getColumns() {
+        return cols;
+    }
+
+    /**
      * @return Rows fetched from query.
      */
-    public List<Object[]> rows() {
+    public List<Object[]> getRows() {
         return rows;
     }
 
     /**
      * @return Whether query has more rows to fetch.
      */
-    public boolean hasMore() {
+    public boolean isHasMore() {
         return hasMore;
     }
 
     /**
      * @return Duration of next page fetching.
      */
-    public long duration() {
+    public long getDuration() {
         return duration;
     }
 
     /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeUuid(out, resNodeId);
+        U.writeString(out, qryId);
+        U.writeCollection(out, cols);
+        U.writeCollection(out, rows);
+        out.writeBoolean(hasMore);
+        out.writeLong(duration);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        resNodeId = U.readUuid(in);
+        qryId = U.readString(in);
+        cols = U.readList(in);
+        rows = U.readList(in);
+        hasMore = in.readBoolean();
+        duration = in.readLong();
+    }
+
+    /** {@inheritDoc} */
     @Override public String toString() {
         return S.toString(VisorQueryResult.class, this);
     }

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResultEx.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResultEx.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResultEx.java
deleted file mode 100644
index 218cb36..0000000
--- a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryResultEx.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * 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.query;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.UUID;
-import org.apache.ignite.internal.util.typedef.internal.S;
-
-/**
- * Result for cache query tasks.
- */
-public class VisorQueryResultEx extends VisorQueryResult {
-    /** */
-    private static final long serialVersionUID = 0L;
-
-    /** Node where query executed. */
-    private final UUID resNodeId;
-
-    /** Query ID to store in node local. */
-    private final String qryId;
-
-    /** Query columns descriptors. */
-    private final Collection<VisorQueryField> cols;
-
-    /**
-     * @param resNodeId Node where query executed.
-     * @param qryId Query ID for future extraction in nextPage() access.
-     * @param cols Columns descriptors.
-     * @param rows Rows fetched from query.
-     * @param hasMore Whether query has more rows to fetch.
-     * @param duration Query duration.
-     */
-    public VisorQueryResultEx(
-        UUID resNodeId,
-        String qryId,
-        Collection<VisorQueryField> cols,
-        List<Object[]> rows,
-        boolean hasMore,
-        long duration
-    ) {
-        super(rows, hasMore, duration);
-
-        this.resNodeId = resNodeId;
-        this.qryId = qryId;
-        this.cols = cols;
-    }
-
-    /**
-     * @return Response node id.
-     */
-    public UUID responseNodeId() {
-        return resNodeId;
-    }
-
-    /**
-     * @return Query id.
-     */
-    public String queryId() {
-        return qryId;
-    }
-
-    /**
-     * @return Columns.
-     */
-    public Collection<VisorQueryField> columns() {
-        return cols;
-    }
-
-    /** {@inheritDoc} */
-    @Override public String toString() {
-        return S.toString(VisorQueryResultEx.class, this);
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/12dfe9e8/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryScanRegexFilter.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryScanRegexFilter.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryScanRegexFilter.java
new file mode 100644
index 0000000..fa4a596
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/query/VisorQueryScanRegexFilter.java
@@ -0,0 +1,59 @@
+/*
+ * 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.query;
+
+import java.util.regex.Pattern;
+import org.apache.ignite.binary.BinaryObject;
+import org.apache.ignite.lang.IgniteBiPredicate;
+
+/**
+ * Filter scan results by specified substring in string presentation of key or value.
+ */
+public class VisorQueryScanRegexFilter implements IgniteBiPredicate<Object, Object> {
+    /** */
+    private static final long serialVersionUID = 0L;
+
+    /** Regex pattern to search data. */
+    private final Pattern ptrn;
+
+    /**
+     * Create filter instance.
+     *
+     * @param caseSensitive Case sensitive flag.
+     * @param regex Regex search flag.
+     * @param ptrn String to search in string presentation of key or value.
+     */
+    public VisorQueryScanRegexFilter(boolean caseSensitive, boolean regex, String ptrn) {
+        int flags = caseSensitive ? 0 : Pattern.CASE_INSENSITIVE;
+
+        this.ptrn = Pattern.compile(regex ? ptrn : ".*?" + Pattern.quote(ptrn) + ".*?", flags);
+    }
+    /**
+     * Check that key or value contains specified string.
+     *
+     * @param key Key object.
+     * @param val Value object.
+     * @return {@code true} when string presentation of key or value contain specified string.
+     */
+    @Override public boolean apply(Object key, Object val) {
+        String k = key instanceof BinaryObject ? VisorQueryUtils.binaryToString((BinaryObject)key) : key.toString();
+        String v = val instanceof BinaryObject ? VisorQueryUtils.binaryToString((BinaryObject)val) : val.toString();
+
+        return ptrn.matcher(k).find() || ptrn.matcher(v).find();
+    }
+}