You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by na...@apache.org on 2022/06/01 12:59:45 UTC

[ignite] branch master updated: IGNITE-14913 Added cache metrics command for Control Script (#9694)

This is an automated email from the ASF dual-hosted git repository.

namelchev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 9003d09e29e IGNITE-14913 Added cache metrics command for Control Script (#9694)
9003d09e29e is described below

commit 9003d09e29e2465e0766be73f0837bc56ae64a24
Author: Ilya Shishkov <sh...@gmail.com>
AuthorDate: Wed Jun 1 15:59:34 2022 +0300

    IGNITE-14913 Added cache metrics command for Control Script (#9694)
---
 docs/_docs/tools/control-script.adoc               |  29 +++
 .../commandline/cache/CacheCommandList.java        |   7 +-
 .../internal/commandline/cache/CacheMetrics.java   | 133 ++++++++++
 .../commandline/cache/CacheSubcommands.java        |   7 +-
 .../testsuites/IgniteControlUtilityTestSuite.java  |   2 +
 .../ignite/util/CacheMetricsCommandTest.java       | 278 +++++++++++++++++++++
 .../visor/cache/metrics/CacheMetricsOperation.java |  53 ++++
 .../visor/cache/metrics/VisorCacheMetricsTask.java | 114 +++++++++
 .../cache/metrics/VisorCacheMetricsTaskArg.java    |  84 +++++++
 .../cache/metrics/VisorCacheMetricsTaskResult.java |  83 ++++++
 .../main/resources/META-INF/classnames.properties  |   5 +
 ...mandHandlerClusterByClassTest_cache_help.output |   7 +
 ...dlerClusterByClassWithSSLTest_cache_help.output |   7 +
 13 files changed, 807 insertions(+), 2 deletions(-)

diff --git a/docs/_docs/tools/control-script.adoc b/docs/_docs/tools/control-script.adoc
index 599bf841de1..d1fe4732356 100644
--- a/docs/_docs/tools/control-script.adoc
+++ b/docs/_docs/tools/control-script.adoc
@@ -1081,3 +1081,32 @@ tab:Window[]
 control.bat --enable-experimental --consistency status
 ----
 --
+
+== Manage cache metrics collection
+
+The command provides an ability to enable, disable or show status of cache metrics collection.
+
+[source, shell]
+----
+control.sh|bat --cache metrics enable|disable|status --caches cache1[,...,cacheN]|--all-caches
+----
+
+Parameters:
+
+[cols="1,3",opts="header"]
+|===
+| Parameter | Description
+| `--caches cache1[,...,cacheN]`| Specifies a comma-separated list of cache names to which operation should be applied.
+| `--all-caches` | Applies operation to all user caches.
+|===
+
+Examples:
+[source, shell]
+----
+# Show metrics statuses for all caches:
+control.sh|bat --cache metrics status --all-caches
+
+# Enable metrics collection for cache-1 and cache-2:
+control.sh|bat --cache metrics enable --caches cache-2,cache-1
+----
+
diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/cache/CacheCommandList.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/cache/CacheCommandList.java
index d11ddcbfbf6..5eed85e63c8 100644
--- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/cache/CacheCommandList.java
+++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/cache/CacheCommandList.java
@@ -87,7 +87,12 @@ public enum CacheCommandList {
     /**
      * Index force rebuild.
      */
-    INDEX_FORCE_REBUILD("indexes_force_rebuild", new CacheIndexesForceRebuild());
+    INDEX_FORCE_REBUILD("indexes_force_rebuild", new CacheIndexesForceRebuild()),
+
+    /**
+     * Enable, disable or show status for cache metrics.
+     */
+    METRICS("metrics", new CacheMetrics());
 
     /** Enumerated values. */
     private static final CacheCommandList[] VALS = values();
diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/cache/CacheMetrics.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/cache/CacheMetrics.java
new file mode 100644
index 00000000000..602ac23eedd
--- /dev/null
+++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/cache/CacheMetrics.java
@@ -0,0 +1,133 @@
+/*
+ * 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.commandline.cache;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Logger;
+import org.apache.ignite.internal.client.GridClient;
+import org.apache.ignite.internal.client.GridClientConfiguration;
+import org.apache.ignite.internal.commandline.AbstractCommand;
+import org.apache.ignite.internal.commandline.Command;
+import org.apache.ignite.internal.commandline.CommandArgIterator;
+import org.apache.ignite.internal.commandline.TaskExecutor;
+import org.apache.ignite.internal.commandline.systemview.SystemViewCommand;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.visor.cache.metrics.CacheMetricsOperation;
+import org.apache.ignite.internal.visor.cache.metrics.VisorCacheMetricsTask;
+import org.apache.ignite.internal.visor.cache.metrics.VisorCacheMetricsTaskArg;
+import org.apache.ignite.internal.visor.cache.metrics.VisorCacheMetricsTaskResult;
+
+import static java.util.Arrays.asList;
+import static org.apache.ignite.internal.commandline.CommandLogger.optional;
+import static org.apache.ignite.internal.commandline.CommandLogger.or;
+import static org.apache.ignite.internal.commandline.cache.CacheSubcommands.METRICS;
+import static org.apache.ignite.internal.visor.cache.metrics.CacheMetricsOperation.DISABLE;
+import static org.apache.ignite.internal.visor.cache.metrics.CacheMetricsOperation.ENABLE;
+import static org.apache.ignite.internal.visor.cache.metrics.CacheMetricsOperation.STATUS;
+import static org.apache.ignite.internal.visor.systemview.VisorSystemViewTask.SimpleType.STRING;
+
+/**
+ * Cache sub-command for a cache metrics collection management. It provides an ability to enable, disable or show status.
+ */
+public class CacheMetrics extends AbstractCommand<VisorCacheMetricsTaskArg> {
+    /** Argument for applying metrics command operation to explicitly specified caches. */
+    public static final String CACHES_ARGUMENT = "--caches";
+
+    /** Argument for applying metrics command operation to all user caches. */
+    public static final String ALL_CACHES_ARGUMENT = "--all-caches";
+
+    /** Incorrect metrics operation message. */
+    public static final String INCORRECT_METRICS_OPERATION_MESSAGE = "Expected correct metrics command operation.";
+
+    /** Incorrect cache argument message. */
+    public static final String INCORRECT_CACHE_ARGUMENT_MESSAGE =
+        String.format("Expected one of these arguments: '%s' or '%s'. Multiple arguments are not allowed.",
+            CACHES_ARGUMENT, ALL_CACHES_ARGUMENT);
+
+    /** Expected caches list message. */
+    public static final String EXPECTED_CACHES_LIST_MESSAGE = "comma-separated list of cache names.";
+
+    /** Task argument. */
+    private VisorCacheMetricsTaskArg arg;
+
+    /** {@inheritDoc} */
+    @Override public Object execute(GridClientConfiguration clientCfg, Logger log) throws Exception {
+        try (GridClient client = Command.startClient(clientCfg)) {
+            VisorCacheMetricsTaskResult res = TaskExecutor.executeTaskByNameOnNode(client,
+                VisorCacheMetricsTask.class.getName(), arg, null, clientCfg);
+
+            List<List<?>> values = new ArrayList<>();
+
+            for (Map.Entry<String, Boolean> e : res.result().entrySet())
+                values.add(asList(e.getKey(), e.getValue() ? "enabled" : "disabled"));
+
+            SystemViewCommand.printTable(asList("Cache Name", "Metrics Status"), asList(STRING, STRING), values, log);
+
+            return null;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public void printUsage(Logger log) {
+        String desc = "Manages user cache metrics collection: enables, disables it or shows status.";
+
+        String cachesArgDesc = CACHES_ARGUMENT + " cache1" + optional(",...,cacheN");
+
+        Map<String, String> paramsDesc = F.asMap(
+            cachesArgDesc, "specifies a comma-separated list of cache names to which operation should be applied.",
+            ALL_CACHES_ARGUMENT, "applies operation to all user caches.");
+
+        usageCache(log, METRICS, desc, paramsDesc, or(ENABLE, DISABLE, STATUS), or(cachesArgDesc, ALL_CACHES_ARGUMENT));
+    }
+
+    /** {@inheritDoc} */
+    @Override public VisorCacheMetricsTaskArg arg() {
+        return arg;
+    }
+
+    /** {@inheritDoc} */
+    @Override public void parseArguments(CommandArgIterator argIter) {
+        CacheMetricsOperation op = CacheMetricsOperation.of(argIter.nextArg(INCORRECT_METRICS_OPERATION_MESSAGE));
+
+        if (op == null)
+            throw new IllegalArgumentException(INCORRECT_METRICS_OPERATION_MESSAGE);
+
+        Set<String> cacheNames;
+
+        String arg = argIter.nextArg(INCORRECT_CACHE_ARGUMENT_MESSAGE);
+
+        if (CACHES_ARGUMENT.equals(arg))
+            cacheNames = new TreeSet<>(argIter.nextStringSet(EXPECTED_CACHES_LIST_MESSAGE));
+        else if (ALL_CACHES_ARGUMENT.equals(arg))
+            cacheNames = Collections.emptySet();
+        else
+            throw new IllegalArgumentException(INCORRECT_CACHE_ARGUMENT_MESSAGE);
+
+        this.arg = new VisorCacheMetricsTaskArg(op, cacheNames);
+    }
+
+    /** {@inheritDoc} */
+    @Override public String name() {
+        return METRICS.text().toUpperCase();
+    }
+}
diff --git a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/cache/CacheSubcommands.java b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/cache/CacheSubcommands.java
index 770e8ead20f..1565dafc95e 100644
--- a/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/cache/CacheSubcommands.java
+++ b/modules/control-utility/src/main/java/org/apache/ignite/internal/commandline/cache/CacheSubcommands.java
@@ -96,7 +96,12 @@ public enum CacheSubcommands {
     /**
      * Destroy caches.
      */
-    DESTROY("destroy", null, new CacheDestroy());
+    DESTROY("destroy", null, new CacheDestroy()),
+
+    /**
+     * Enable / disable cache metrics collection or show metrics collection status.
+     */
+    METRICS("metrics", null, new CacheMetrics());
 
     /** Enumerated values. */
     private static final CacheSubcommands[] VALS = values();
diff --git a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java
index f020609d046..02206c76b8b 100644
--- a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java
+++ b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java
@@ -22,6 +22,7 @@ import org.apache.ignite.events.BaselineEventsRemoteTest;
 import org.apache.ignite.internal.commandline.CommandHandlerParsingTest;
 import org.apache.ignite.internal.commandline.indexreader.IgniteIndexReaderTest;
 import org.apache.ignite.internal.processors.security.GridCommandHandlerSslWithSecurityTest;
+import org.apache.ignite.util.CacheMetricsCommandTest;
 import org.apache.ignite.util.GridCommandHandlerBrokenIndexTest;
 import org.apache.ignite.util.GridCommandHandlerCheckIndexesInlineSizeTest;
 import org.apache.ignite.util.GridCommandHandlerClusterByClassTest;
@@ -103,6 +104,7 @@ import org.junit.runners.Suite;
     SystemViewCommandTest.class,
     MetricCommandTest.class,
     PerformanceStatisticsCommandTest.class,
+    CacheMetricsCommandTest.class,
 
     IgniteIndexReaderTest.class
 })
diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/CacheMetricsCommandTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/CacheMetricsCommandTest.java
new file mode 100644
index 00000000000..ad7d99ebe30
--- /dev/null
+++ b/modules/control-utility/src/test/java/org/apache/ignite/util/CacheMetricsCommandTest.java
@@ -0,0 +1,278 @@
+/*
+ * 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.util;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.commandline.cache.CacheMetrics;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.util.typedef.G;
+import org.apache.ignite.internal.visor.cache.metrics.CacheMetricsOperation;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.junit.Test;
+
+import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_INVALID_ARGUMENTS;
+import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK;
+import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_UNEXPECTED_ERROR;
+import static org.apache.ignite.internal.commandline.CommandList.CACHE;
+import static org.apache.ignite.internal.commandline.cache.CacheMetrics.ALL_CACHES_ARGUMENT;
+import static org.apache.ignite.internal.commandline.cache.CacheMetrics.CACHES_ARGUMENT;
+import static org.apache.ignite.internal.commandline.cache.CacheMetrics.EXPECTED_CACHES_LIST_MESSAGE;
+import static org.apache.ignite.internal.commandline.cache.CacheMetrics.INCORRECT_CACHE_ARGUMENT_MESSAGE;
+import static org.apache.ignite.internal.commandline.cache.CacheMetrics.INCORRECT_METRICS_OPERATION_MESSAGE;
+import static org.apache.ignite.internal.commandline.cache.CacheSubcommands.METRICS;
+import static org.apache.ignite.internal.util.lang.GridFunc.asMap;
+
+/**
+ * Test for {@link CacheMetrics} command.
+ */
+public class CacheMetricsCommandTest extends GridCommandHandlerAbstractTest {
+    /** Enable operation. */
+    private static final String ENABLE = CacheMetricsOperation.ENABLE.toString();
+
+    /** Disable operation. */
+    private static final String DISABLE = CacheMetricsOperation.DISABLE.toString();
+
+    /** Status operation. */
+    private static final String STATUS = CacheMetricsOperation.STATUS.toString();
+
+    /** Cache one. */
+    private static final String CACHE_ONE = "cache-1";
+
+    /** Cache two. */
+    private static final String CACHE_TWO = "cache-2";
+
+    /** {@inheritDoc} */
+    @Override public void beforeTest() throws Exception {
+        super.beforeTest();
+
+        injectTestSystemOut();
+        persistenceEnable(false);
+        autoConfirmation = false;
+
+        startGrids(2);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTest() throws Exception {
+        super.afterTest();
+
+        stopAllGrids();
+    }
+
+    /**
+     * Tests metrics enable / disable operations.
+     */
+    @Test
+    public void testEnableDisable() {
+        // Test empty cluster
+        checkExecutionSuccess(Collections.emptyMap(), ENABLE, ALL_CACHES_ARGUMENT);
+
+        createCachesWithMetrics(asMap(CACHE_ONE, false, CACHE_TWO, true));
+
+        checkExecutionSuccess(asMap(CACHE_ONE, true), ENABLE, CACHES_ARGUMENT, CACHE_ONE);
+        checkExecutionSuccess(asMap(CACHE_TWO, false), DISABLE, CACHES_ARGUMENT, CACHE_TWO);
+
+        // Cache list with duplicates
+        String cacheNames = String.join(",", CACHE_TWO, CACHE_ONE, CACHE_ONE, CACHE_TWO);
+
+        checkExecutionSuccess(asMap(CACHE_ONE, false, CACHE_TWO, false), DISABLE, CACHES_ARGUMENT, cacheNames);
+
+        checkExecutionSuccess(asMap(CACHE_ONE, true, CACHE_TWO, true), ENABLE, ALL_CACHES_ARGUMENT);
+    }
+
+    /**
+     * Tests metrics status operation.
+     */
+    @Test
+    public void testStatus() {
+        // Test empty cluster
+        checkExecutionSuccess(Collections.emptyMap(), STATUS, ALL_CACHES_ARGUMENT);
+
+        createCachesWithMetrics(asMap(CACHE_ONE, false, CACHE_TWO, true));
+
+        checkExecutionSuccess(asMap(CACHE_ONE, false), STATUS, CACHES_ARGUMENT, CACHE_ONE);
+        checkExecutionSuccess(asMap(CACHE_TWO, true), STATUS, CACHES_ARGUMENT, CACHE_TWO);
+
+        // Cache list with duplicates
+        String cacheNames = String.join(",", CACHE_TWO, CACHE_ONE, CACHE_ONE, CACHE_TWO);
+
+        checkExecutionSuccess(asMap(CACHE_ONE, false, CACHE_TWO, true), STATUS, CACHES_ARGUMENT, cacheNames);
+
+        checkExecutionSuccess(asMap(CACHE_ONE, false, CACHE_TWO, true), STATUS, ALL_CACHES_ARGUMENT);
+    }
+
+    /**
+     * Tests metrics operations for a non-existing cache.
+     */
+    @Test
+    public void testNotFoundCache() {
+        createCachesWithMetrics(asMap(CACHE_ONE, false));
+
+        String descriptorsNotFoundMsg = "One or more cache descriptors not found [caches=[" + CACHE_ONE + ", " +
+            CACHE_TWO + ']';
+
+        checkExecutionError(descriptorsNotFoundMsg, ENABLE, CACHES_ARGUMENT, CACHE_ONE + ',' + CACHE_TWO);
+
+        // Check that metrics statuses was not changed
+        checkClusterMetrics(asMap(CACHE_ONE, false));
+
+        checkExecutionError("Cache does not exist: " + CACHE_TWO, STATUS, CACHES_ARGUMENT, CACHE_ONE + ',' + CACHE_TWO);
+    }
+
+    /** */
+    @Test
+    public void testInvalidArguments() {
+        String checkArgs = "Check arguments. ";
+
+        // Check when no operation passed
+        checkInvalidArguments(checkArgs + INCORRECT_METRICS_OPERATION_MESSAGE);
+
+        // Check when unknown operation passed
+        checkInvalidArguments(checkArgs + INCORRECT_METRICS_OPERATION_MESSAGE, "bad-command");
+
+        // Check when no --caches/--all-caches arguments passed
+        checkInvalidArguments(checkArgs + INCORRECT_CACHE_ARGUMENT_MESSAGE, ENABLE);
+        checkInvalidArguments(checkArgs + INCORRECT_CACHE_ARGUMENT_MESSAGE, STATUS);
+
+        String invalidCacheListFullMsg = "Check arguments. Expected " + EXPECTED_CACHES_LIST_MESSAGE;
+
+        // Check when --caches argument passed without list of caches
+        checkInvalidArguments(invalidCacheListFullMsg, ENABLE, CACHES_ARGUMENT);
+        checkInvalidArguments(invalidCacheListFullMsg, STATUS, CACHES_ARGUMENT);
+
+        String incorrectCacheArgFullMsg = checkArgs + INCORRECT_CACHE_ARGUMENT_MESSAGE;
+
+        // Check when unknown argument is passed after metric operation
+        checkInvalidArguments(incorrectCacheArgFullMsg, ENABLE, "--arg");
+        checkInvalidArguments(incorrectCacheArgFullMsg, STATUS, "--arg");
+
+        String unexpectedCacheArgMsg = "Unexpected argument of --cache subcommand: ";
+
+        // Check when extra argument passed after correct command
+        checkInvalidArguments(unexpectedCacheArgMsg + ALL_CACHES_ARGUMENT, ENABLE, CACHES_ARGUMENT, CACHE_ONE,
+            ALL_CACHES_ARGUMENT);
+        checkInvalidArguments(unexpectedCacheArgMsg + CACHES_ARGUMENT, STATUS, ALL_CACHES_ARGUMENT, CACHES_ARGUMENT,
+            CACHE_ONE);
+
+        // Check when after --caches argument extra argument is passed instead of list of caches
+        checkInvalidArguments(unexpectedCacheArgMsg + ALL_CACHES_ARGUMENT, ENABLE, CACHES_ARGUMENT, ALL_CACHES_ARGUMENT);
+    }
+
+    /**
+     * Check execution of metric operation and parse resulting table.
+     *
+     * @param expStatuses Expected table entries in command output.
+     * @param args Command arguments.
+     */
+    private void checkExecutionSuccess(Map<String, Boolean> expStatuses, String... args) {
+        exec(EXIT_CODE_OK, args);
+
+        checkOutput(expStatuses, testOut.toString());
+
+        checkClusterMetrics(expStatuses);
+    }
+
+    /**
+     * @param expExitCode Expected exit code.
+     * @param args Command arguments.
+     */
+    private void exec(int expExitCode, String... args) {
+        String[] fullArgs = F.concat(new String[] {CACHE.text(), METRICS.text()}, args);
+
+        int exitCode = execute(fullArgs);
+        assertEquals("Unexpected exit code", expExitCode, exitCode);
+    }
+
+    /** */
+    private void checkInvalidArguments(String expOut, String... args) {
+        checkExecutionAndOutput(EXIT_CODE_INVALID_ARGUMENTS, expOut, args);
+    }
+
+    /** */
+    private void checkExecutionError(String expOut, String... args) {
+        checkExecutionAndOutput(EXIT_CODE_UNEXPECTED_ERROR, expOut, args);
+    }
+
+    /**
+     * Check command execution and results.
+     *
+     * @param expExitCode Expected exit code.
+     * @param expOut Expected command output.
+     * @param args Command arguments.
+     */
+    private void checkExecutionAndOutput(int expExitCode, String expOut, String... args) {
+        exec(expExitCode, args);
+
+        GridTestUtils.assertContains(log, testOut.toString(), expOut);
+    }
+
+    /**
+     * @param expStatuses Expected metrics statuses.
+     * @param testOutStr Test output.
+     */
+    private void checkOutput(Map<String, Boolean> expStatuses, String testOutStr) {
+        for (Map.Entry<String, Boolean> entry : expStatuses.entrySet()) {
+            String cacheName = entry.getKey();
+            String metricsStatus = entry.getValue() ? "enabled" : "disabled";
+
+            Matcher cacheStatusMatcher = Pattern.compile(cacheName + "\\s+" + metricsStatus).matcher(testOutStr);
+
+            String msg = String.format("Unexpected count of table entries for metrics: [cacheName=%s, metricsStatus=%s]",
+                cacheName, metricsStatus);
+
+            int cnt = 0;
+
+            while (cacheStatusMatcher.find())
+                cnt++;
+
+            assertEquals(msg, 1, cnt);
+        }
+    }
+
+    /**
+     * @param metricsStatuses Metrics statuses.
+     */
+    private void createCachesWithMetrics(Map<String, Boolean> metricsStatuses) {
+        for (Map.Entry<String, Boolean> nameAndState : metricsStatuses.entrySet()) {
+            grid(0).getOrCreateCache(new CacheConfiguration<>()
+                .setName(nameAndState.getKey())
+                .setStatisticsEnabled(nameAndState.getValue()));
+        }
+
+        checkClusterMetrics(metricsStatuses);
+    }
+
+    /**
+     * @param expStatuses Expected cache metrics statuses.
+     */
+    private void checkClusterMetrics(Map<String, Boolean> expStatuses) {
+        for (Ignite ignite : G.allGrids()) {
+            for (String cacheName : expStatuses.keySet()) {
+                Boolean cacheMetricsEnabled = ignite.cache(cacheName).metrics().isStatisticsEnabled();
+
+                assertEquals("Unexpected metrics mode for cache: " + cacheName, expStatuses.get(cacheName),
+                    cacheMetricsEnabled);
+            }
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/CacheMetricsOperation.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/CacheMetricsOperation.java
new file mode 100644
index 00000000000..133e454fca8
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/CacheMetricsOperation.java
@@ -0,0 +1,53 @@
+/*
+ * 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.metrics;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Enum for cache metrics command operations.
+ */
+public enum CacheMetricsOperation {
+    /** Enable operation. */
+    ENABLE,
+
+    /** Disable operation. */
+    DISABLE,
+
+    /** Status operation. */
+    STATUS;
+
+    /**
+     * @param strRep String representation of operation.
+     *
+     * @return Operation corresponding to the specified string representation.
+     */
+    public static @Nullable CacheMetricsOperation of(String strRep) {
+        for (CacheMetricsOperation op : values()) {
+            if (op.name().equalsIgnoreCase(strRep))
+                return op;
+        }
+
+        return null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return super.toString().toLowerCase();
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/VisorCacheMetricsTask.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/VisorCacheMetricsTask.java
new file mode 100644
index 00000000000..5071ded10e6
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/VisorCacheMetricsTask.java
@@ -0,0 +1,114 @@
+/*
+ * 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.metrics;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeMap;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.processors.cache.IgniteInternalCache;
+import org.apache.ignite.internal.processors.task.GridInternal;
+import org.apache.ignite.internal.processors.task.GridVisorManagementTask;
+import org.apache.ignite.internal.util.typedef.F;
+import org.apache.ignite.internal.visor.VisorJob;
+import org.apache.ignite.internal.visor.VisorOneNodeTask;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.visor.cache.metrics.CacheMetricsOperation.ENABLE;
+
+/**
+ * Task for a cache metrics command.
+ */
+@GridInternal
+@GridVisorManagementTask
+public class VisorCacheMetricsTask extends VisorOneNodeTask<VisorCacheMetricsTaskArg, VisorCacheMetricsTaskResult> {
+    /** Serial version uid. */
+    private static final long serialVersionUID = 0L;
+
+    /** {@inheritDoc} */
+    @Override protected VisorCacheMetricsJob job(VisorCacheMetricsTaskArg arg) {
+        return new VisorCacheMetricsJob(arg, false);
+    }
+
+    /**
+     * Job returns {@link Map} with names of processed caches paired with corresponding metrics collection statuses or
+     * exception, caught during execution of job.
+     * Results are passed into instance of wrapper class {@link VisorCacheMetricsTaskResult}.
+     */
+    private static class VisorCacheMetricsJob extends VisorJob<VisorCacheMetricsTaskArg, VisorCacheMetricsTaskResult> {
+        /** Serial version uid. */
+        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 VisorCacheMetricsJob(@Nullable VisorCacheMetricsTaskArg arg, boolean debug) {
+            super(arg, debug);
+        }
+
+        /** {@inheritDoc} */
+        @Override protected VisorCacheMetricsTaskResult run(@Nullable VisorCacheMetricsTaskArg arg)
+            throws IgniteException {
+            if (arg != null) {
+                Collection<String> cacheNames = F.isEmpty(arg.cacheNames()) ? ignite.cacheNames() : arg.cacheNames();
+
+                try {
+                    switch (arg.operation()) {
+                        case ENABLE:
+                        case DISABLE:
+                            ignite.cluster().enableStatistics(cacheNames, ENABLE == arg.operation());
+
+                            return new VisorCacheMetricsTaskResult(cacheMetricsStatus(cacheNames));
+
+                        case STATUS:
+                            return new VisorCacheMetricsTaskResult(cacheMetricsStatus(cacheNames));
+
+                        default:
+                            throw new IllegalStateException("Unexpected value: " + arg.operation());
+                    }
+                }
+                catch (Exception e) {
+                    return new VisorCacheMetricsTaskResult(e);
+                }
+            }
+
+            return null;
+        }
+
+        /**
+         * @param cacheNames Cache names.
+         */
+        private Map<String, Boolean> cacheMetricsStatus(Collection<String> cacheNames) {
+            Map<String, Boolean> cacheMetricsStatus = new TreeMap<>();
+
+            for (String cacheName : cacheNames) {
+                IgniteInternalCache<?, ?> cachex = ignite.cachex(cacheName);
+
+                if (cachex != null)
+                    cacheMetricsStatus.put(cacheName, cachex.clusterMetrics().isStatisticsEnabled());
+                else
+                    throw new IgniteException("Cache does not exist: " + cacheName);
+            }
+
+            return cacheMetricsStatus;
+        }
+    }
+}
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/VisorCacheMetricsTaskArg.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/VisorCacheMetricsTaskArg.java
new file mode 100644
index 00000000000..c6574e40532
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/VisorCacheMetricsTaskArg.java
@@ -0,0 +1,84 @@
+/*
+ * 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.metrics;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Collections;
+import java.util.Set;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Task argument for {@link VisorCacheMetricsTask}.
+ */
+public class VisorCacheMetricsTaskArg extends IgniteDataTransferObject {
+    /** Serial version uid. */
+    private static final long serialVersionUID = 0L;
+
+    /** Metrics command operation. */
+    private CacheMetricsOperation op;
+
+    /** Caches which will be processed. If not set, operation will affect all caches. */
+    private Set<String> cacheNames;
+
+    /**
+     * Default constructor.
+     */
+    public VisorCacheMetricsTaskArg() {
+        // No-op.
+    }
+
+    /**
+     * @param op Metrics command operation.
+     * @param cacheNames Names of the caches, which should be processed.
+     */
+    public VisorCacheMetricsTaskArg(CacheMetricsOperation op, Set<String> cacheNames) {
+        this.op = op;
+        this.cacheNames = cacheNames == null ? null : Collections.unmodifiableSet(cacheNames);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeEnum(out, op);
+        U.writeCollection(out, cacheNames);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException,
+        ClassNotFoundException {
+        op = U.readEnum(in, CacheMetricsOperation.class);
+        cacheNames = U.readSet(in);
+    }
+
+    /**
+     * @return Metrics command operation.
+     */
+    public CacheMetricsOperation operation() {
+        return op;
+    }
+
+    /**
+     * @return Caches which will be processed. If not set, operation will affect all caches.
+     */
+    public Set<String> cacheNames() {
+        return Collections.unmodifiableSet(cacheNames);
+    }
+}
+
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/VisorCacheMetricsTaskResult.java b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/VisorCacheMetricsTaskResult.java
new file mode 100644
index 00000000000..e232f7048f2
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/visor/cache/metrics/VisorCacheMetricsTaskResult.java
@@ -0,0 +1,83 @@
+/*
+ * 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.metrics;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Collections;
+import java.util.Map;
+import org.apache.ignite.internal.dto.IgniteDataTransferObject;
+import org.apache.ignite.internal.util.typedef.internal.U;
+
+/**
+ * Result wrapper for {@link VisorCacheMetricsTask}.
+ */
+public class VisorCacheMetricsTaskResult extends IgniteDataTransferObject {
+    /** Serial version uid. */
+    private static final long serialVersionUID = 0L;
+
+    /** Task result. */
+    private Map<String, Boolean> result;
+
+    /** Task execution error. */
+    private Exception error;
+
+    /**
+     * Default constructor.
+     */
+    public VisorCacheMetricsTaskResult() {
+        // No-op.
+    }
+
+    /**
+     * @param result Task execution result.
+     */
+    public VisorCacheMetricsTaskResult(Map<String, Boolean> result) {
+        this.result = Collections.unmodifiableMap(result);
+    }
+
+    /**
+     * @param error Task execution error.
+     */
+    public VisorCacheMetricsTaskResult(Exception error) {
+        this.error = error;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void writeExternalData(ObjectOutput out) throws IOException {
+        U.writeMap(out, result);
+        out.writeObject(error);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void readExternalData(byte protoVer, ObjectInput in) throws IOException, ClassNotFoundException {
+        result = U.readMap(in);
+        error = (Exception)in.readObject();
+    }
+
+    /**
+     * Get task result or task execution error.
+     */
+    public Map<String, Boolean> result() throws Exception {
+        if (error != null)
+            throw error;
+
+        return Collections.unmodifiableMap(result);
+    }
+}
diff --git a/modules/core/src/main/resources/META-INF/classnames.properties b/modules/core/src/main/resources/META-INF/classnames.properties
index 266910ea138..5aeb89669a7 100644
--- a/modules/core/src/main/resources/META-INF/classnames.properties
+++ b/modules/core/src/main/resources/META-INF/classnames.properties
@@ -2051,6 +2051,11 @@ org.apache.ignite.internal.visor.cache.VisorCacheMetrics
 org.apache.ignite.internal.visor.cache.VisorCacheMetricsCollectorTask
 org.apache.ignite.internal.visor.cache.VisorCacheMetricsCollectorTask$VisorCacheMetricsCollectorJob
 org.apache.ignite.internal.visor.cache.VisorCacheMetricsCollectorTaskArg
+org.apache.ignite.internal.visor.cache.metrics.CacheMetricsOperation
+org.apache.ignite.internal.visor.cache.metrics.VisorCacheMetricsTask
+org.apache.ignite.internal.visor.cache.metrics.VisorCacheMetricsTask$VisorCacheMetricsJob
+org.apache.ignite.internal.visor.cache.metrics.VisorCacheMetricsTaskArg
+org.apache.ignite.internal.visor.cache.metrics.VisorCacheMetricsTaskResult
 org.apache.ignite.internal.visor.cache.VisorCacheModifyTask
 org.apache.ignite.internal.visor.cache.VisorCacheModifyTask$VisorCacheModifyJob
 org.apache.ignite.internal.visor.cache.VisorCacheModifyTaskArg
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output
index d175fcce598..585370b4963 100644
--- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output
+++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output
@@ -83,6 +83,13 @@ Arguments: --cache help --yes
       --cache-names  - Comma-separated list of cache names for which indexes should be rebuilt.
       --group-names  - Comma-separated list of cache group names for which indexes should be rebuilt.
 
+  --cache metrics enable|disable|status --caches cache1[,...,cacheN]|--all-caches
+    Manages user cache metrics collection: enables, disables it or shows status.
+
+    Parameters:
+      --caches cache1[,...,cacheN]  - specifies a comma-separated list of cache names to which operation should be applied.
+      --all-caches                  - applies operation to all user caches.
+
 Command [CACHE] finished with code: 0
 Control utility has completed execution at: <!any!>
 Execution time: <!any!>
diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output
index d175fcce598..585370b4963 100644
--- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output
+++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output
@@ -83,6 +83,13 @@ Arguments: --cache help --yes
       --cache-names  - Comma-separated list of cache names for which indexes should be rebuilt.
       --group-names  - Comma-separated list of cache group names for which indexes should be rebuilt.
 
+  --cache metrics enable|disable|status --caches cache1[,...,cacheN]|--all-caches
+    Manages user cache metrics collection: enables, disables it or shows status.
+
+    Parameters:
+      --caches cache1[,...,cacheN]  - specifies a comma-separated list of cache names to which operation should be applied.
+      --all-caches                  - applies operation to all user caches.
+
 Command [CACHE] finished with code: 0
 Control utility has completed execution at: <!any!>
 Execution time: <!any!>