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

apkhmv pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new d2c381a939 IGNITE-18305 CLI command for list of enabled metrics (#1802)
d2c381a939 is described below

commit d2c381a9398a8596e95887487ca9c1a95684a589
Author: Vadim Pakhnushev <[email protected]>
AuthorDate: Thu Mar 23 14:56:39 2023 +0300

    IGNITE-18305 CLI command for list of enabled metrics (#1802)
---
 .../cli/call/metric/ItEnabledMetricCallsTest.java  | 106 ++++++++++++++++++++
 .../cli/call/metric/ItMetricCallsTest.java         |  56 +++++++----
 .../metric/ItEnabledNodeMetricCommandTest.java     |  88 ++++++++++++++++
 .../commands/metric/ItNodeMetricCommandTest.java   |  15 ++-
 .../internal/rest/ItGeneratedRestClientTest.java   |   9 +-
 ...ricListCall.java => NodeMetricSetListCall.java} |  20 ++--
 ...leCall.java => NodeMetricSourceEnableCall.java} |   8 +-
 ...t.java => NodeMetricSourceEnableCallInput.java} |  26 ++---
 ...ListCall.java => NodeMetricSourceListCall.java} |  16 +--
 .../cluster/topology/LogicalTopologyCommand.java   |   4 +-
 .../topology/LogicalTopologyReplCommand.java       |   4 +-
 .../cluster/topology/PhysicalTopologyCommand.java  |   4 +-
 .../topology/PhysicalTopologyReplCommand.java      |   4 +-
 .../cli/commands/metric/MetricSourceMixin.java     |  10 +-
 .../commands/node/metric/NodeMetricCommand.java    |   5 +-
 .../node/metric/NodeMetricReplCommand.java         |   5 +-
 ...eCommand.java => NodeMetricSetListCommand.java} |  26 +++--
 ...mand.java => NodeMetricSetListReplCommand.java} |  27 +++--
 ...plCommand.java => NodeMetricSourceCommand.java} |  14 +--
 ...nd.java => NodeMetricSourceDisableCommand.java} |   6 +-
 ...ava => NodeMetricSourceDisableReplCommand.java} |   6 +-
 ...and.java => NodeMetricSourceEnableCommand.java} |   6 +-
 ...java => NodeMetricSourceEnableReplCommand.java} |   6 +-
 ...mmand.java => NodeMetricSourceListCommand.java} |  21 ++--
 ...d.java => NodeMetricSourceListReplCommand.java} |  21 ++--
 ...mmand.java => NodeMetricSourceReplCommand.java} |  14 +--
 .../internal/cli/commands/sql/SqlCommand.java      |   5 +-
 .../internal/cli/commands/sql/SqlReplCommand.java  |   5 +-
 .../completer/DynamicCompleterActivationPoint.java |  13 +++
 .../MetricSourceDynamicCompleterFactory.java}      |  33 +++---
 .../repl/registry/MetricRegistry.java}             |  19 ++--
 .../repl/registry/impl/MetricRegistryImpl.java     |  71 +++++++++++++
 .../cli/decorators/DefaultDecoratorRegistry.java   |   4 +-
 .../cli/decorators/MetricSetListDecorator.java     |  59 +++++++++++
 ...corator.java => MetricSourceListDecorator.java} |  41 +++++---
 .../cli/decorators/PlainTopologyDecorator.java     |  42 --------
 .../cli/decorators/SqlQueryResultDecorator.java    |   4 +-
 .../internal/cli/decorators/TableDecorator.java    |  13 ++-
 .../internal/cli/decorators/TopologyDecorator.java |  17 +++-
 .../internal/cli/util/PlainTableRenderer.java      |   4 +-
 .../internal/cli/IgniteCliInterfaceTest.java       |  37 +++++--
 .../cli/commands/UrlOptionsNegativeTest.java       |  28 +++---
 .../internal/cli/util/PlainTableRendererTest.java  |  10 +-
 modules/rest-api/openapi/openapi.yaml              | 111 ++++++++++++++++-----
 .../rest/api/deployment/DeploymentCodeApi.java     |   6 +-
 .../{MetricSourceDto.java => MetricDto.java}       |  40 ++++----
 .../{MetricSourceDto.java => MetricSetDto.java}    |  42 ++++----
 .../internal/rest/api/metric/MetricSourceDto.java  |   4 +-
 .../internal/rest/api/metric/NodeMetricApi.java    |  14 ++-
 .../rest/metrics/NodeMetricController.java         |  18 +++-
 50 files changed, 810 insertions(+), 357 deletions(-)

diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/call/metric/ItEnabledMetricCallsTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/call/metric/ItEnabledMetricCallsTest.java
new file mode 100644
index 0000000000..d3c295e2b2
--- /dev/null
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/call/metric/ItEnabledMetricCallsTest.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.cli.call.metric;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import jakarta.inject.Inject;
+import java.util.List;
+import org.apache.ignite.internal.cli.call.CallInitializedIntegrationTestBase;
+import org.apache.ignite.internal.cli.call.node.metric.NodeMetricSetListCall;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceEnableCall;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceEnableCallInput;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceListCall;
+import org.apache.ignite.internal.cli.core.call.CallOutput;
+import org.apache.ignite.rest.client.model.Metric;
+import org.apache.ignite.rest.client.model.MetricSet;
+import org.apache.ignite.rest.client.model.MetricSource;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/** Tests for metrics calls with enabled "jvm" metrics source. */
+class ItEnabledMetricCallsTest extends CallInitializedIntegrationTestBase {
+
+    @Inject
+    NodeMetricSourceEnableCall nodeMetricSourceEnableCall;
+
+    @Inject
+    NodeMetricSourceListCall nodeMetricSourceListCall;
+
+    @Inject
+    NodeMetricSetListCall nodeMetricSetListCall;
+
+    @BeforeAll
+    void beforeAll() {
+        var inputEnable = NodeMetricSourceEnableCallInput.builder()
+                .endpointUrl(NODE_URL)
+                .srcName("jvm")
+                .enable(true)
+                .build();
+
+        nodeMetricSourceEnableCall.execute(inputEnable);
+    }
+
+    @Test
+    @DisplayName("Should display enabled jvm node metric source when cluster 
is up and running")
+    void nodeMetricSourcesList() {
+        // When
+        CallOutput<List<MetricSource>> output = 
nodeMetricSourceListCall.execute(urlInput);
+
+        // Then
+        assertThat(output.hasError()).isFalse();
+
+        MetricSource[] expectedMetricSources = {
+                new MetricSource().name("jvm").enabled(true),
+                new MetricSource().name("client.handler").enabled(false)
+        };
+
+        // And
+        
assertThat(output.body()).containsExactlyInAnyOrder(expectedMetricSources);
+    }
+
+    @Test
+    @DisplayName("Should display node metric sets list when cluster is up and 
running")
+    void nodeMetricSetsListEnabled() {
+        // When
+        CallOutput<List<MetricSet>> output = 
nodeMetricSetListCall.execute(urlInput);
+
+        // Then
+        assertThat(output.hasError()).isFalse();
+
+        // And
+        Metric[] expectedMetrics = {
+                new Metric().name("memory.heap.init").desc("Initial amount of 
heap memory"),
+                new Metric().name("memory.heap.used").desc("Current used 
amount of heap memory"),
+                new Metric().name("memory.heap.committed").desc("Committed 
amount of heap memory"),
+                new Metric().name("memory.heap.max").desc("Maximum amount of 
heap memory"),
+                new Metric().name("memory.non-heap.init").desc("Initial amount 
of non-heap memory"),
+                new Metric().name("memory.non-heap.used").desc("Used amount of 
non-heap memory"),
+                new Metric().name("memory.non-heap.committed").desc("Committed 
amount of non-heap memory"),
+                new Metric().name("memory.non-heap.max").desc("Maximum amount 
of non-heap memory")
+        };
+
+        assertAll(
+                () -> assertThat(output.body()).hasSize(1),
+                () -> 
assertThat(output.body().get(0).getName()).isEqualTo("jvm"),
+                () -> 
assertThat(output.body().get(0).getMetrics()).containsExactlyInAnyOrder(expectedMetrics)
+        );
+    }
+}
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/call/metric/ItMetricCallsTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/call/metric/ItMetricCallsTest.java
index 008761d16d..fdfeffd1f6 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/call/metric/ItMetricCallsTest.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/call/metric/ItMetricCallsTest.java
@@ -18,63 +18,75 @@
 package org.apache.ignite.internal.cli.call.metric;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.hamcrest.Matchers.containsInAnyOrder;
 
 import jakarta.inject.Inject;
 import java.util.List;
 import org.apache.ignite.internal.cli.call.CallInitializedIntegrationTestBase;
-import org.apache.ignite.internal.cli.call.node.metric.NodeMetricEnableCall;
-import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricEnableCallInput;
-import org.apache.ignite.internal.cli.call.node.metric.NodeMetricListCall;
+import org.apache.ignite.internal.cli.call.node.metric.NodeMetricSetListCall;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceEnableCall;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceEnableCallInput;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceListCall;
 import org.apache.ignite.internal.cli.core.call.CallOutput;
-import org.apache.ignite.internal.cli.core.call.StringCallInput;
+import org.apache.ignite.rest.client.model.MetricSet;
 import org.apache.ignite.rest.client.model.MetricSource;
-import org.hamcrest.MatcherAssert;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
-/** Tests for {@link NodeMetricListCall} and {@link NodeMetricEnableCall}. */
+/** Tests for metrics calls. */
 class ItMetricCallsTest extends CallInitializedIntegrationTestBase {
 
     @Inject
-    NodeMetricListCall nodeMetricListCall;
+    NodeMetricSourceListCall nodeMetricSourceListCall;
 
     @Inject
-    NodeMetricEnableCall nodeMetricEnableCall;
+    NodeMetricSetListCall nodeMetricSetListCall;
 
-    @Test
-    @DisplayName("Should display empty node metric list when cluster is up and 
running")
-    void nodeMetricList() {
-        // Given
-        var input = new StringCallInput(NODE_URL);
+    @Inject
+    NodeMetricSourceEnableCall nodeMetricSourceEnableCall;
 
+    @Test
+    @DisplayName("Should display disabled jvm node metric source when cluster 
is up and running")
+    void nodeMetricSourcesList() {
         // When
-        CallOutput<List<MetricSource>> output = 
nodeMetricListCall.execute(input);
+        CallOutput<List<MetricSource>> output = 
nodeMetricSourceListCall.execute(urlInput);
 
         // Then
         assertThat(output.hasError()).isFalse();
 
-        List<MetricSource> expectedMetricSources = List.of(
+        MetricSource[] expectedMetricSources = {
                 new MetricSource().name("jvm").enabled(false),
                 new MetricSource().name("client.handler").enabled(false)
-        );
+        };
+
+        // And
+        
assertThat(output.body()).containsExactlyInAnyOrder(expectedMetricSources);
+    }
+
+    @Test
+    @DisplayName("Should display empty node metric sets list when cluster is 
up and running")
+    void nodeMetricSetsListEmpty() {
+        // When
+        CallOutput<List<MetricSet>> output = 
nodeMetricSetListCall.execute(urlInput);
+
+        // Then
+        assertThat(output.hasError()).isFalse();
 
         // And
-        MatcherAssert.assertThat(output.body(), 
containsInAnyOrder(expectedMetricSources.toArray()));
+        assertThat(output.body()).isEmpty();
     }
 
     @Test
     @DisplayName("Should display error message when enabling nonexistent 
metric source and is cluster up and running")
     void nodeMetricEnable() {
         // Given
-        var input = NodeMetricEnableCallInput.builder()
+        var input = NodeMetricSourceEnableCallInput.builder()
                 .endpointUrl(NODE_URL)
                 .srcName("no.such.metric")
                 .enable(true)
                 .build();
 
         // When
-        CallOutput<String> output = nodeMetricEnableCall.execute(input);
+        CallOutput<String> output = nodeMetricSourceEnableCall.execute(input);
 
         // Then
         assertThat(output.hasError()).isTrue();
@@ -84,14 +96,14 @@ class ItMetricCallsTest extends 
CallInitializedIntegrationTestBase {
     @DisplayName("Should display error message when disabling nonexistent 
metric source and is cluster up and running")
     void nodeMetricDisable() {
         // Given
-        var input = NodeMetricEnableCallInput.builder()
+        var input = NodeMetricSourceEnableCallInput.builder()
                 .endpointUrl(NODE_URL)
                 .srcName("no.such.metric")
                 .enable(false)
                 .build();
 
         // When
-        CallOutput<String> output = nodeMetricEnableCall.execute(input);
+        CallOutput<String> output = nodeMetricSourceEnableCall.execute(input);
 
         // Then
         assertThat(output.hasError()).isTrue();
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/metric/ItEnabledNodeMetricCommandTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/metric/ItEnabledNodeMetricCommandTest.java
new file mode 100644
index 0000000000..01fd6bcf32
--- /dev/null
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/metric/ItEnabledNodeMetricCommandTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.cli.commands.metric;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+
+import jakarta.inject.Inject;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceEnableCall;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceEnableCallInput;
+import 
org.apache.ignite.internal.cli.commands.CliCommandTestInitializedIntegrationBase;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+/** Tests for node metric commands with enabled metrics. */
+class ItEnabledNodeMetricCommandTest extends 
CliCommandTestInitializedIntegrationBase {
+
+    private static final String NL = System.lineSeparator();
+
+    @Inject
+    NodeMetricSourceEnableCall nodeMetricSourceEnableCall;
+
+    @BeforeAll
+    void beforeAll() {
+        var inputEnable = NodeMetricSourceEnableCallInput.builder()
+                .endpointUrl(NODE_URL)
+                .srcName("jvm")
+                .enable(true)
+                .build();
+
+        nodeMetricSourceEnableCall.execute(inputEnable);
+    }
+
+    @Test
+    @DisplayName("Should display enabled jvm metric source when valid node-url 
is given")
+    void nodeMetricSourcesList() {
+        // When list node metric sources with valid url
+        execute("node", "metric", "source", "list", "--plain", "--node-url", 
NODE_URL);
+
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                () -> assertOutputIs("Set name\tEnabled" + NL
+                        + "jvm\tenabled" + NL
+                        + "client.handler\tdisabled" + NL)
+        );
+    }
+
+    @Test
+    @DisplayName("Should display node metrics list when valid node-url is 
given")
+    void nodeMetricEnableNonexistent() {
+        // When list node metric with valid url
+        execute("node", "metric", "list", "--plain", "--node-url", NODE_URL);
+
+        // Then
+        assertAll(
+                this::assertExitCodeIsZero,
+                this::assertErrOutputIsEmpty,
+                () -> assertOutputIs("Set name\tMetric name\tDescription" + NL
+                        + "jvm\t\t" + NL
+                        + "\tmemory.heap.committed\tCommitted amount of heap 
memory" + NL
+                        + "\tmemory.heap.max\tMaximum amount of heap memory" + 
NL
+                        + "\tmemory.non-heap.max\tMaximum amount of non-heap 
memory" + NL
+                        + "\tmemory.non-heap.init\tInitial amount of non-heap 
memory" + NL
+                        + "\tmemory.non-heap.committed\tCommitted amount of 
non-heap memory" + NL
+                        + "\tmemory.non-heap.used\tUsed amount of non-heap 
memory" + NL
+                        + "\tmemory.heap.used\tCurrent used amount of heap 
memory" + NL
+                        + "\tmemory.heap.init\tInitial amount of heap memory" 
+ NL
+                )
+        );
+    }
+}
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/metric/ItNodeMetricCommandTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/metric/ItNodeMetricCommandTest.java
index d139fd70c4..97a10a783a 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/metric/ItNodeMetricCommandTest.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/metric/ItNodeMetricCommandTest.java
@@ -26,19 +26,18 @@ import org.junit.jupiter.api.Test;
 /** Tests for node metric commands. */
 class ItNodeMetricCommandTest extends CliCommandTestInitializedIntegrationBase 
{
     @Test
-    @DisplayName("Should display empty node metric list when valid node-url is 
given")
+    @DisplayName("Should display disabled jvm metric source when valid 
node-url is given")
     void nodeMetricList() {
         // When list node metric with valid url
-        execute("node", "metric", "list", "--node-url", NODE_URL);
+        execute("node", "metric", "source", "list", "--plain", "--node-url", 
NODE_URL);
 
         // Then
         assertAll(
                 this::assertExitCodeIsZero,
                 this::assertErrOutputIsEmpty,
-                () -> assertOutputIs("Enabled metric sources:" + 
System.lineSeparator()
-                        + "Disabled metric sources:" + System.lineSeparator()
-                        + "jvm" + System.lineSeparator()
-                        + "client.handler" + System.lineSeparator())
+                () -> assertOutputIs("Set name\tEnabled" + 
System.lineSeparator()
+                        + "jvm\tdisabled" + System.lineSeparator()
+                        + "client.handler\tdisabled" + System.lineSeparator())
         );
     }
 
@@ -46,7 +45,7 @@ class ItNodeMetricCommandTest extends 
CliCommandTestInitializedIntegrationBase {
     @DisplayName("Should display error message when enabling nonexistent 
metric source and valid node-url is given")
     void nodeMetricEnableNonexistent() {
         // When list node metric with valid url
-        execute("node", "metric", "enable", "no.such.metric", "--node-url", 
NODE_URL);
+        execute("node", "metric", "source", "enable", "no.such.metric", 
"--node-url", NODE_URL);
 
         // Then
         assertAll(
@@ -60,7 +59,7 @@ class ItNodeMetricCommandTest extends 
CliCommandTestInitializedIntegrationBase {
     @DisplayName("Should display error message when disabling nonexistent 
metric source and valid node-url is given")
     void nodeMetricDisableNonexistent() {
         // When list node metric with valid url
-        execute("node", "metric", "disable", "no.such.metric", "--node-url", 
NODE_URL);
+        execute("node", "metric", "source", "disable", "no.such.metric", 
"--node-url", NODE_URL);
 
         // Then
         assertAll(
diff --git 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
index 347f4751d6..13a48dfa01 100644
--- 
a/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
+++ 
b/modules/cli/src/integrationTest/java/org/apache/ignite/internal/rest/ItGeneratedRestClientTest.java
@@ -352,13 +352,18 @@ public class ItGeneratedRestClientTest {
     }
 
     @Test
-    void nodeMetricList() throws ApiException {
+    void nodeMetricSourcesList() throws ApiException {
         List<MetricSource> metricSources = List.of(
                 new MetricSource().name("jvm").enabled(false),
                 new MetricSource().name("client.handler").enabled(false)
         );
 
-        assertThat(nodeMetricApi.listNodeMetrics(), 
containsInAnyOrder(metricSources.toArray()));
+        assertThat(nodeMetricApi.listNodeMetricSources(), 
containsInAnyOrder(metricSources.toArray()));
+    }
+
+    @Test
+    void nodeMetricSetsList() throws ApiException {
+        assertThat(nodeMetricApi.listNodeMetricSets(), empty());
     }
 
     @Test
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricListCall.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSetListCall.java
similarity index 74%
copy from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricListCall.java
copy to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSetListCall.java
index 6b339414bb..d7bb97915b 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricListCall.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSetListCall.java
@@ -23,33 +23,33 @@ import org.apache.ignite.internal.cli.core.ApiClientFactory;
 import org.apache.ignite.internal.cli.core.call.Call;
 import org.apache.ignite.internal.cli.core.call.CallOutput;
 import org.apache.ignite.internal.cli.core.call.DefaultCallOutput;
-import org.apache.ignite.internal.cli.core.call.StringCallInput;
+import org.apache.ignite.internal.cli.core.call.UrlCallInput;
 import org.apache.ignite.internal.cli.core.exception.IgniteCliApiException;
 import org.apache.ignite.rest.client.api.NodeMetricApi;
 import org.apache.ignite.rest.client.invoker.ApiException;
-import org.apache.ignite.rest.client.model.MetricSource;
+import org.apache.ignite.rest.client.model.MetricSet;
 
-/** Lists node metric sources. */
+/** Lists node metric sets. */
 @Singleton
-public class NodeMetricListCall implements Call<StringCallInput, 
List<MetricSource>> {
+public class NodeMetricSetListCall implements Call<UrlCallInput, 
List<MetricSet>> {
     private final ApiClientFactory clientFactory;
 
-    public NodeMetricListCall(ApiClientFactory clientFactory) {
+    public NodeMetricSetListCall(ApiClientFactory clientFactory) {
         this.clientFactory = clientFactory;
     }
 
     /** {@inheritDoc} */
     @Override
-    public CallOutput<List<MetricSource>> execute(StringCallInput input) {
+    public CallOutput<List<MetricSet>> execute(UrlCallInput input) {
 
         try {
-            return DefaultCallOutput.success(listNodeMetrics(input));
+            return DefaultCallOutput.success(listNodeMetricSets(input));
         } catch (ApiException | IllegalArgumentException e) {
-            return DefaultCallOutput.failure(new IgniteCliApiException(e, 
input.getString()));
+            return DefaultCallOutput.failure(new IgniteCliApiException(e, 
input.getUrl()));
         }
     }
 
-    private List<MetricSource> listNodeMetrics(StringCallInput input) throws 
ApiException {
-        return new 
NodeMetricApi(clientFactory.getClient(input.getString())).listNodeMetrics();
+    private List<MetricSet> listNodeMetricSets(UrlCallInput input) throws 
ApiException {
+        return new 
NodeMetricApi(clientFactory.getClient(input.getUrl())).listNodeMetricSets();
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricEnableCall.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSourceEnableCall.java
similarity index 86%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricEnableCall.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSourceEnableCall.java
index c4628e8229..9f3576668e 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricEnableCall.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSourceEnableCall.java
@@ -28,16 +28,16 @@ import org.apache.ignite.rest.client.invoker.ApiException;
 
 /** Enables or disables metric source. */
 @Singleton
-public class NodeMetricEnableCall implements Call<NodeMetricEnableCallInput, 
String> {
+public class NodeMetricSourceEnableCall implements 
Call<NodeMetricSourceEnableCallInput, String> {
     private final ApiClientFactory clientFactory;
 
-    public NodeMetricEnableCall(ApiClientFactory clientFactory) {
+    public NodeMetricSourceEnableCall(ApiClientFactory clientFactory) {
         this.clientFactory = clientFactory;
     }
 
     /** {@inheritDoc} */
     @Override
-    public CallOutput<String> execute(NodeMetricEnableCallInput input) {
+    public CallOutput<String> execute(NodeMetricSourceEnableCallInput input) {
         NodeMetricApi api = createApiClient(input);
 
         try {
@@ -53,7 +53,7 @@ public class NodeMetricEnableCall implements 
Call<NodeMetricEnableCallInput, Str
         }
     }
 
-    private NodeMetricApi createApiClient(NodeMetricEnableCallInput input) {
+    private NodeMetricApi createApiClient(NodeMetricSourceEnableCallInput 
input) {
         return new 
NodeMetricApi(clientFactory.getClient(input.getEndpointUrl()));
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricEnableCallInput.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSourceEnableCallInput.java
similarity index 69%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricEnableCallInput.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSourceEnableCallInput.java
index b31562415d..5389ffbc10 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricEnableCallInput.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSourceEnableCallInput.java
@@ -19,8 +19,8 @@ package org.apache.ignite.internal.cli.call.node.metric;
 
 import org.apache.ignite.internal.cli.core.call.CallInput;
 
-/** Input for {@link NodeMetricEnableCall}. */
-public class NodeMetricEnableCallInput implements CallInput {
+/** Input for {@link NodeMetricSourceEnableCall}. */
+public class NodeMetricSourceEnableCallInput implements CallInput {
     /** Metric source name. */
     private final String srcName;
 
@@ -30,7 +30,7 @@ public class NodeMetricEnableCallInput implements CallInput {
     /** endpoint URL. */
     private final String endpointUrl;
 
-    private NodeMetricEnableCallInput(String srcName, boolean enable, String 
endpointUrl) {
+    private NodeMetricSourceEnableCallInput(String srcName, boolean enable, 
String endpointUrl) {
         this.srcName = srcName;
         this.enable = enable;
         this.endpointUrl = endpointUrl;
@@ -39,10 +39,10 @@ public class NodeMetricEnableCallInput implements CallInput 
{
     /**
      * Builder method.
      *
-     * @return Builder for {@link NodeMetricEnableCallInput}.
+     * @return Builder for {@link NodeMetricSourceEnableCallInput}.
      */
-    public static NodeMetricEnableCallInputBuilder builder() {
-        return new NodeMetricEnableCallInputBuilder();
+    public static NodeMetricSourceEnableCallInputBuilder builder() {
+        return new NodeMetricSourceEnableCallInputBuilder();
     }
 
     /**
@@ -73,9 +73,9 @@ public class NodeMetricEnableCallInput implements CallInput {
     }
 
     /**
-     * Builder for {@link NodeMetricEnableCallInput}.
+     * Builder for {@link NodeMetricSourceEnableCallInput}.
      */
-    public static class NodeMetricEnableCallInputBuilder {
+    public static class NodeMetricSourceEnableCallInputBuilder {
 
         private String srcName;
 
@@ -83,23 +83,23 @@ public class NodeMetricEnableCallInput implements CallInput 
{
 
         private String endpointUrl;
 
-        public NodeMetricEnableCallInputBuilder srcName(String srcName) {
+        public NodeMetricSourceEnableCallInputBuilder srcName(String srcName) {
             this.srcName = srcName;
             return this;
         }
 
-        public NodeMetricEnableCallInputBuilder enable(boolean enable) {
+        public NodeMetricSourceEnableCallInputBuilder enable(boolean enable) {
             this.enable = enable;
             return this;
         }
 
-        public NodeMetricEnableCallInputBuilder endpointUrl(String 
endpointUrl) {
+        public NodeMetricSourceEnableCallInputBuilder endpointUrl(String 
endpointUrl) {
             this.endpointUrl = endpointUrl;
             return this;
         }
 
-        public NodeMetricEnableCallInput build() {
-            return new NodeMetricEnableCallInput(srcName, enable, endpointUrl);
+        public NodeMetricSourceEnableCallInput build() {
+            return new NodeMetricSourceEnableCallInput(srcName, enable, 
endpointUrl);
         }
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricListCall.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSourceListCall.java
similarity index 77%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricListCall.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSourceListCall.java
index 6b339414bb..da036db076 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricListCall.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/call/node/metric/NodeMetricSourceListCall.java
@@ -23,7 +23,7 @@ import org.apache.ignite.internal.cli.core.ApiClientFactory;
 import org.apache.ignite.internal.cli.core.call.Call;
 import org.apache.ignite.internal.cli.core.call.CallOutput;
 import org.apache.ignite.internal.cli.core.call.DefaultCallOutput;
-import org.apache.ignite.internal.cli.core.call.StringCallInput;
+import org.apache.ignite.internal.cli.core.call.UrlCallInput;
 import org.apache.ignite.internal.cli.core.exception.IgniteCliApiException;
 import org.apache.ignite.rest.client.api.NodeMetricApi;
 import org.apache.ignite.rest.client.invoker.ApiException;
@@ -31,25 +31,25 @@ import org.apache.ignite.rest.client.model.MetricSource;
 
 /** Lists node metric sources. */
 @Singleton
-public class NodeMetricListCall implements Call<StringCallInput, 
List<MetricSource>> {
+public class NodeMetricSourceListCall implements Call<UrlCallInput, 
List<MetricSource>> {
     private final ApiClientFactory clientFactory;
 
-    public NodeMetricListCall(ApiClientFactory clientFactory) {
+    public NodeMetricSourceListCall(ApiClientFactory clientFactory) {
         this.clientFactory = clientFactory;
     }
 
     /** {@inheritDoc} */
     @Override
-    public CallOutput<List<MetricSource>> execute(StringCallInput input) {
+    public CallOutput<List<MetricSource>> execute(UrlCallInput input) {
 
         try {
-            return DefaultCallOutput.success(listNodeMetrics(input));
+            return DefaultCallOutput.success(listNodeMetricSources(input));
         } catch (ApiException | IllegalArgumentException e) {
-            return DefaultCallOutput.failure(new IgniteCliApiException(e, 
input.getString()));
+            return DefaultCallOutput.failure(new IgniteCliApiException(e, 
input.getUrl()));
         }
     }
 
-    private List<MetricSource> listNodeMetrics(StringCallInput input) throws 
ApiException {
-        return new 
NodeMetricApi(clientFactory.getClient(input.getString())).listNodeMetrics();
+    private List<MetricSource> listNodeMetricSources(UrlCallInput input) 
throws ApiException {
+        return new 
NodeMetricApi(clientFactory.getClient(input.getUrl())).listNodeMetricSources();
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyCommand.java
index 24b9a2f0a9..5afa5a53cf 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyCommand.java
@@ -28,7 +28,6 @@ import 
org.apache.ignite.internal.cli.commands.cluster.ClusterUrlProfileMixin;
 import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.internal.cli.core.call.UrlCallInput;
 import 
org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
-import org.apache.ignite.internal.cli.decorators.PlainTopologyDecorator;
 import org.apache.ignite.internal.cli.decorators.TopologyDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Mixin;
@@ -52,12 +51,11 @@ public class LogicalTopologyCommand extends BaseCommand 
implements Callable<Inte
     /** {@inheritDoc} */
     @Override
     public Integer call() {
-        TopologyDecorator topologyDecorator = plain ? new 
PlainTopologyDecorator() : new TopologyDecorator();
         return CallExecutionPipeline.builder(call)
                 .inputProvider(() -> new 
UrlCallInput(clusterUrl.getClusterUrl()))
                 .output(spec.commandLine().getOut())
                 .errOutput(spec.commandLine().getErr())
-                .decorator(topologyDecorator)
+                .decorator(new TopologyDecorator(plain))
                 .exceptionHandler(new ClusterNotInitializedExceptionHandler(
                         "Cannot show logical topology", "ignite cluster init"
                 ))
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyReplCommand.java
index 3069cab8cd..2eb008b52c 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/LogicalTopologyReplCommand.java
@@ -28,7 +28,6 @@ import 
org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestio
 import org.apache.ignite.internal.cli.core.call.UrlCallInput;
 import 
org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
 import org.apache.ignite.internal.cli.core.flow.builder.Flows;
-import org.apache.ignite.internal.cli.decorators.PlainTopologyDecorator;
 import org.apache.ignite.internal.cli.decorators.TopologyDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Mixin;
@@ -55,12 +54,11 @@ public class LogicalTopologyReplCommand extends BaseCommand 
implements Runnable
     /** {@inheritDoc} */
     @Override
     public void run() {
-        TopologyDecorator topologyDecorator = plain ? new 
PlainTopologyDecorator() : new TopologyDecorator();
         question.askQuestionIfNotConnected(clusterUrl.getClusterUrl())
                 .map(UrlCallInput::new)
                 .then(Flows.fromCall(call))
                 .exceptionHandler(new 
ClusterNotInitializedExceptionHandler("Cannot show logical topology", "cluster 
init"))
-                .print(topologyDecorator)
+                .print(new TopologyDecorator(plain))
                 .start();
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyCommand.java
index b6c32e3cc4..51e6b81a5b 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyCommand.java
@@ -27,7 +27,6 @@ import org.apache.ignite.internal.cli.commands.BaseCommand;
 import org.apache.ignite.internal.cli.commands.cluster.ClusterUrlProfileMixin;
 import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
 import org.apache.ignite.internal.cli.core.call.UrlCallInput;
-import org.apache.ignite.internal.cli.decorators.PlainTopologyDecorator;
 import org.apache.ignite.internal.cli.decorators.TopologyDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Mixin;
@@ -51,12 +50,11 @@ public class PhysicalTopologyCommand extends BaseCommand 
implements Callable<Int
     /** {@inheritDoc} */
     @Override
     public Integer call() {
-        TopologyDecorator topologyDecorator = plain ? new 
PlainTopologyDecorator() : new TopologyDecorator();
         return CallExecutionPipeline.builder(call)
                 .inputProvider(() -> new 
UrlCallInput(clusterUrl.getClusterUrl()))
                 .output(spec.commandLine().getOut())
                 .errOutput(spec.commandLine().getErr())
-                .decorator(topologyDecorator)
+                .decorator(new TopologyDecorator(plain))
                 .verbose(verbose)
                 .build()
                 .runPipeline();
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyReplCommand.java
index 81e251234e..dbae939d88 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/cluster/topology/PhysicalTopologyReplCommand.java
@@ -27,7 +27,6 @@ import 
org.apache.ignite.internal.cli.commands.cluster.ClusterUrlMixin;
 import 
org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestion;
 import org.apache.ignite.internal.cli.core.call.UrlCallInput;
 import org.apache.ignite.internal.cli.core.flow.builder.Flows;
-import org.apache.ignite.internal.cli.decorators.PlainTopologyDecorator;
 import org.apache.ignite.internal.cli.decorators.TopologyDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Mixin;
@@ -54,11 +53,10 @@ public class PhysicalTopologyReplCommand extends 
BaseCommand implements Runnable
     /** {@inheritDoc} */
     @Override
     public void run() {
-        TopologyDecorator topologyDecorator = plain ? new 
PlainTopologyDecorator() : new TopologyDecorator();
         question.askQuestionIfNotConnected(clusterUrl.getClusterUrl())
                 .map(UrlCallInput::new)
                 .then(Flows.fromCall(call))
-                .print(topologyDecorator)
+                .print(new TopologyDecorator(plain))
                 .start();
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/metric/MetricSourceMixin.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/metric/MetricSourceMixin.java
index b4a367a414..8c4ac3671d 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/metric/MetricSourceMixin.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/metric/MetricSourceMixin.java
@@ -17,7 +17,7 @@
 
 package org.apache.ignite.internal.cli.commands.metric;
 
-import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricEnableCallInput;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceEnableCallInput;
 import picocli.CommandLine.Parameters;
 
 /** Mixin class for metric source name, provides source name parameter and 
constructs call input. */
@@ -26,16 +26,16 @@ public class MetricSourceMixin {
     @Parameters(index = "0", description = "Metric source name")
     private String srcName;
 
-    public NodeMetricEnableCallInput buildEnableCallInput(String endpointUrl) {
+    public NodeMetricSourceEnableCallInput buildEnableCallInput(String 
endpointUrl) {
         return buildCallInput(endpointUrl, true);
     }
 
-    public NodeMetricEnableCallInput buildDisableCallInput(String endpointUrl) 
{
+    public NodeMetricSourceEnableCallInput buildDisableCallInput(String 
endpointUrl) {
         return buildCallInput(endpointUrl, false);
     }
 
-    private NodeMetricEnableCallInput buildCallInput(String endpointUrl, 
boolean enable) {
-        return NodeMetricEnableCallInput.builder()
+    private NodeMetricSourceEnableCallInput buildCallInput(String endpointUrl, 
boolean enable) {
+        return NodeMetricSourceEnableCallInput.builder()
                 .endpointUrl(endpointUrl)
                 .srcName(srcName)
                 .enable(enable)
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricCommand.java
index 0a43305964..ccb26549f9 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricCommand.java
@@ -23,9 +23,8 @@ import picocli.CommandLine.Command;
 /** Node metric command. */
 @Command(name = "metric",
         subcommands = {
-                NodeMetricEnableCommand.class,
-                NodeMetricDisableCommand.class,
-                NodeMetricListCommand.class
+                NodeMetricSetListCommand.class,
+                NodeMetricSourceCommand.class
         },
         description = "Node metric operations")
 public class NodeMetricCommand extends BaseCommand {
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricReplCommand.java
index 7f253c6a76..d387499298 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricReplCommand.java
@@ -23,9 +23,8 @@ import picocli.CommandLine.Command;
 /** Node metric command in REPL. */
 @Command(name = "metric",
         subcommands = {
-                NodeMetricEnableReplCommand.class,
-                NodeMetricDisableReplCommand.class,
-                NodeMetricListReplCommand.class
+                NodeMetricSetListReplCommand.class,
+                NodeMetricSourceReplCommand.class
         },
         description = "Node metric operations")
 public class NodeMetricReplCommand extends BaseCommand {
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricDisableCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSetListCommand.java
similarity index 68%
copy from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricDisableCommand.java
copy to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSetListCommand.java
index fdaad9a48d..9867ecda54 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricDisableCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSetListCommand.java
@@ -17,39 +17,43 @@
 
 package org.apache.ignite.internal.cli.commands.node.metric;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
+
 import jakarta.inject.Inject;
 import java.util.concurrent.Callable;
-import org.apache.ignite.internal.cli.call.node.metric.NodeMetricEnableCall;
+import org.apache.ignite.internal.cli.call.node.metric.NodeMetricSetListCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
-import org.apache.ignite.internal.cli.commands.metric.MetricSourceMixin;
 import org.apache.ignite.internal.cli.commands.node.NodeUrlProfileMixin;
 import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
-import 
org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
+import org.apache.ignite.internal.cli.core.call.UrlCallInput;
+import org.apache.ignite.internal.cli.decorators.MetricSetListDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Mixin;
+import picocli.CommandLine.Option;
 
-/** Command that disables node metric source. */
-@Command(name = "disable", description = "Disables node metric source")
-public class NodeMetricDisableCommand extends BaseCommand implements 
Callable<Integer> {
+/** Command that lists node metrics. */
+@Command(name = "list", description = "Lists node metrics")
+public class NodeMetricSetListCommand extends BaseCommand implements 
Callable<Integer> {
     /** Node URL option. */
     @Mixin
     private NodeUrlProfileMixin nodeUrl;
 
-    @Mixin
-    private MetricSourceMixin metricSource;
+    @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
+    private boolean plain;
 
     @Inject
-    private NodeMetricEnableCall call;
+    private NodeMetricSetListCall call;
 
     /** {@inheritDoc} */
     @Override
     public Integer call() {
         return CallExecutionPipeline.builder(call)
-                .inputProvider(() -> 
metricSource.buildDisableCallInput(nodeUrl.getNodeUrl()))
+                .inputProvider(() -> new UrlCallInput(nodeUrl.getNodeUrl()))
                 .output(spec.commandLine().getOut())
                 .errOutput(spec.commandLine().getErr())
+                .decorator(new MetricSetListDecorator(plain))
                 .verbose(verbose)
-                .exceptionHandler(new 
ClusterNotInitializedExceptionHandler("Cannot disable metrics", "cluster init"))
                 .build()
                 .runPipeline();
     }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricListReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSetListReplCommand.java
similarity index 67%
copy from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricListReplCommand.java
copy to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSetListReplCommand.java
index 0c32af3f63..5bbc6b9b4b 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricListReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSetListReplCommand.java
@@ -17,27 +17,33 @@
 
 package org.apache.ignite.internal.cli.commands.node.metric;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
+
 import jakarta.inject.Inject;
-import org.apache.ignite.internal.cli.call.node.metric.NodeMetricListCall;
+import org.apache.ignite.internal.cli.call.node.metric.NodeMetricSetListCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
 import org.apache.ignite.internal.cli.commands.node.NodeUrlMixin;
 import 
org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestion;
-import org.apache.ignite.internal.cli.core.call.StringCallInput;
-import 
org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
+import org.apache.ignite.internal.cli.core.call.UrlCallInput;
 import org.apache.ignite.internal.cli.core.flow.builder.Flows;
-import org.apache.ignite.internal.cli.decorators.MetricListDecorator;
+import org.apache.ignite.internal.cli.decorators.MetricSetListDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Mixin;
+import picocli.CommandLine.Option;
 
-/** Command that lists node metric sources in REPL mode. */
-@Command(name = "list", description = "Lists node metric sources")
-public class NodeMetricListReplCommand extends BaseCommand implements Runnable 
{
+/** Command that lists node metrics in REPL mode. */
+@Command(name = "list", description = "Lists node metrics")
+public class NodeMetricSetListReplCommand extends BaseCommand implements 
Runnable {
     /** Node URL option. */
     @Mixin
     private NodeUrlMixin nodeUrl;
 
+    @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
+    private boolean plain;
+
     @Inject
-    private NodeMetricListCall call;
+    private NodeMetricSetListCall call;
 
     @Inject
     private ConnectToClusterQuestion question;
@@ -46,10 +52,9 @@ public class NodeMetricListReplCommand extends BaseCommand 
implements Runnable {
     @Override
     public void run() {
         question.askQuestionIfNotConnected(nodeUrl.getNodeUrl())
-                .map(StringCallInput::new)
+                .map(UrlCallInput::new)
                 .then(Flows.fromCall(call))
-                .exceptionHandler(new 
ClusterNotInitializedExceptionHandler("Cannot list metrics", "cluster init"))
-                .print(new MetricListDecorator())
+                .print(new MetricSetListDecorator(plain))
                 .start();
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceCommand.java
similarity index 74%
copy from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricReplCommand.java
copy to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceCommand.java
index 7f253c6a76..965b5471bf 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceCommand.java
@@ -20,13 +20,13 @@ package org.apache.ignite.internal.cli.commands.node.metric;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
 import picocli.CommandLine.Command;
 
-/** Node metric command in REPL. */
-@Command(name = "metric",
+/** Node metric sources command. */
+@Command(name = "source",
         subcommands = {
-                NodeMetricEnableReplCommand.class,
-                NodeMetricDisableReplCommand.class,
-                NodeMetricListReplCommand.class
+                NodeMetricSourceEnableCommand.class,
+                NodeMetricSourceDisableCommand.class,
+                NodeMetricSourceListCommand.class,
         },
-        description = "Node metric operations")
-public class NodeMetricReplCommand extends BaseCommand {
+        description = "Node metric sources operations")
+public class NodeMetricSourceCommand extends BaseCommand {
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricDisableCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceDisableCommand.java
similarity index 93%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricDisableCommand.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceDisableCommand.java
index fdaad9a48d..562deb9c31 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricDisableCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceDisableCommand.java
@@ -19,7 +19,7 @@ package org.apache.ignite.internal.cli.commands.node.metric;
 
 import jakarta.inject.Inject;
 import java.util.concurrent.Callable;
-import org.apache.ignite.internal.cli.call.node.metric.NodeMetricEnableCall;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceEnableCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
 import org.apache.ignite.internal.cli.commands.metric.MetricSourceMixin;
 import org.apache.ignite.internal.cli.commands.node.NodeUrlProfileMixin;
@@ -30,7 +30,7 @@ import picocli.CommandLine.Mixin;
 
 /** Command that disables node metric source. */
 @Command(name = "disable", description = "Disables node metric source")
-public class NodeMetricDisableCommand extends BaseCommand implements 
Callable<Integer> {
+public class NodeMetricSourceDisableCommand extends BaseCommand implements 
Callable<Integer> {
     /** Node URL option. */
     @Mixin
     private NodeUrlProfileMixin nodeUrl;
@@ -39,7 +39,7 @@ public class NodeMetricDisableCommand extends BaseCommand 
implements Callable<In
     private MetricSourceMixin metricSource;
 
     @Inject
-    private NodeMetricEnableCall call;
+    private NodeMetricSourceEnableCall call;
 
     /** {@inheritDoc} */
     @Override
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricDisableReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceDisableReplCommand.java
similarity index 93%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricDisableReplCommand.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceDisableReplCommand.java
index 39c0e38852..bf8d525f9f 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricDisableReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceDisableReplCommand.java
@@ -18,7 +18,7 @@
 package org.apache.ignite.internal.cli.commands.node.metric;
 
 import jakarta.inject.Inject;
-import org.apache.ignite.internal.cli.call.node.metric.NodeMetricEnableCall;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceEnableCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
 import org.apache.ignite.internal.cli.commands.metric.MetricSourceMixin;
 import org.apache.ignite.internal.cli.commands.node.NodeUrlMixin;
@@ -30,7 +30,7 @@ import picocli.CommandLine.Mixin;
 
 /** Command that disables node metric source in REPL mode. */
 @Command(name = "disable", description = "Disables node metric source")
-public class NodeMetricDisableReplCommand extends BaseCommand implements 
Runnable {
+public class NodeMetricSourceDisableReplCommand extends BaseCommand implements 
Runnable {
     /** Node URL option. */
     @Mixin
     private NodeUrlMixin nodeUrl;
@@ -39,7 +39,7 @@ public class NodeMetricDisableReplCommand extends BaseCommand 
implements Runnabl
     private MetricSourceMixin metricSource;
 
     @Inject
-    private NodeMetricEnableCall call;
+    private NodeMetricSourceEnableCall call;
 
     @Inject
     private ConnectToClusterQuestion question;
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricEnableCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceEnableCommand.java
similarity index 93%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricEnableCommand.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceEnableCommand.java
index f4f2bd6772..e82605220c 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricEnableCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceEnableCommand.java
@@ -19,7 +19,7 @@ package org.apache.ignite.internal.cli.commands.node.metric;
 
 import jakarta.inject.Inject;
 import java.util.concurrent.Callable;
-import org.apache.ignite.internal.cli.call.node.metric.NodeMetricEnableCall;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceEnableCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
 import org.apache.ignite.internal.cli.commands.metric.MetricSourceMixin;
 import org.apache.ignite.internal.cli.commands.node.NodeUrlProfileMixin;
@@ -30,7 +30,7 @@ import picocli.CommandLine.Mixin;
 
 /** Command that enables node metric source. */
 @Command(name = "enable", description = "Enables node metric source")
-public class NodeMetricEnableCommand extends BaseCommand implements 
Callable<Integer> {
+public class NodeMetricSourceEnableCommand extends BaseCommand implements 
Callable<Integer> {
     /** Node URL option. */
     @Mixin
     private NodeUrlProfileMixin nodeUrl;
@@ -39,7 +39,7 @@ public class NodeMetricEnableCommand extends BaseCommand 
implements Callable<Int
     private MetricSourceMixin metricSource;
 
     @Inject
-    private NodeMetricEnableCall call;
+    private NodeMetricSourceEnableCall call;
 
     /** {@inheritDoc} */
     @Override
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricEnableReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceEnableReplCommand.java
similarity index 93%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricEnableReplCommand.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceEnableReplCommand.java
index be7d604898..84cc518371 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricEnableReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceEnableReplCommand.java
@@ -18,7 +18,7 @@
 package org.apache.ignite.internal.cli.commands.node.metric;
 
 import jakarta.inject.Inject;
-import org.apache.ignite.internal.cli.call.node.metric.NodeMetricEnableCall;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceEnableCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
 import org.apache.ignite.internal.cli.commands.metric.MetricSourceMixin;
 import org.apache.ignite.internal.cli.commands.node.NodeUrlMixin;
@@ -30,7 +30,7 @@ import picocli.CommandLine.Mixin;
 
 /** Command that enables node metric source in REPL mode. */
 @Command(name = "enable", description = "Enables node metric source")
-public class NodeMetricEnableReplCommand extends BaseCommand implements 
Runnable {
+public class NodeMetricSourceEnableReplCommand extends BaseCommand implements 
Runnable {
     /** Node URL option. */
     @Mixin
     private NodeUrlMixin nodeUrl;
@@ -39,7 +39,7 @@ public class NodeMetricEnableReplCommand extends BaseCommand 
implements Runnable
     private MetricSourceMixin metricSource;
 
     @Inject
-    private NodeMetricEnableCall call;
+    private NodeMetricSourceEnableCall call;
 
     @Inject
     private ConnectToClusterQuestion question;
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricListCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceListCommand.java
similarity index 73%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricListCommand.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceListCommand.java
index f273a65619..8447df2a42 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricListCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceListCommand.java
@@ -17,36 +17,43 @@
 
 package org.apache.ignite.internal.cli.commands.node.metric;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
+
 import jakarta.inject.Inject;
 import java.util.concurrent.Callable;
-import org.apache.ignite.internal.cli.call.node.metric.NodeMetricListCall;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceListCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
 import org.apache.ignite.internal.cli.commands.node.NodeUrlProfileMixin;
 import org.apache.ignite.internal.cli.core.call.CallExecutionPipeline;
-import org.apache.ignite.internal.cli.core.call.StringCallInput;
+import org.apache.ignite.internal.cli.core.call.UrlCallInput;
 import 
org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
-import org.apache.ignite.internal.cli.decorators.MetricListDecorator;
+import org.apache.ignite.internal.cli.decorators.MetricSourceListDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Mixin;
+import picocli.CommandLine.Option;
 
 /** Command that lists node metric sources. */
 @Command(name = "list", description = "Lists node metric sources")
-public class NodeMetricListCommand extends BaseCommand implements 
Callable<Integer> {
+public class NodeMetricSourceListCommand extends BaseCommand implements 
Callable<Integer> {
     /** Node URL option. */
     @Mixin
     private NodeUrlProfileMixin nodeUrl;
 
+    @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
+    private boolean plain;
+
     @Inject
-    private NodeMetricListCall call;
+    private NodeMetricSourceListCall call;
 
     /** {@inheritDoc} */
     @Override
     public Integer call() {
         return CallExecutionPipeline.builder(call)
-                .inputProvider(() -> new StringCallInput(nodeUrl.getNodeUrl()))
+                .inputProvider(() -> new UrlCallInput(nodeUrl.getNodeUrl()))
                 .output(spec.commandLine().getOut())
                 .errOutput(spec.commandLine().getErr())
-                .decorator(new MetricListDecorator())
+                .decorator(new MetricSourceListDecorator(plain))
                 .verbose(verbose)
                 .exceptionHandler(new 
ClusterNotInitializedExceptionHandler("Cannot list metrics", "cluster init"))
                 .build()
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricListReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceListReplCommand.java
similarity index 74%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricListReplCommand.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceListReplCommand.java
index 0c32af3f63..99bcc97b31 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricListReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceListReplCommand.java
@@ -17,27 +17,34 @@
 
 package org.apache.ignite.internal.cli.commands.node.metric;
 
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION;
+import static 
org.apache.ignite.internal.cli.commands.Options.Constants.PLAIN_OPTION_DESC;
+
 import jakarta.inject.Inject;
-import org.apache.ignite.internal.cli.call.node.metric.NodeMetricListCall;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceListCall;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
 import org.apache.ignite.internal.cli.commands.node.NodeUrlMixin;
 import 
org.apache.ignite.internal.cli.commands.questions.ConnectToClusterQuestion;
-import org.apache.ignite.internal.cli.core.call.StringCallInput;
+import org.apache.ignite.internal.cli.core.call.UrlCallInput;
 import 
org.apache.ignite.internal.cli.core.exception.handler.ClusterNotInitializedExceptionHandler;
 import org.apache.ignite.internal.cli.core.flow.builder.Flows;
-import org.apache.ignite.internal.cli.decorators.MetricListDecorator;
+import org.apache.ignite.internal.cli.decorators.MetricSourceListDecorator;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Mixin;
+import picocli.CommandLine.Option;
 
 /** Command that lists node metric sources in REPL mode. */
 @Command(name = "list", description = "Lists node metric sources")
-public class NodeMetricListReplCommand extends BaseCommand implements Runnable 
{
+public class NodeMetricSourceListReplCommand extends BaseCommand implements 
Runnable {
     /** Node URL option. */
     @Mixin
     private NodeUrlMixin nodeUrl;
 
+    @Option(names = PLAIN_OPTION, description = PLAIN_OPTION_DESC)
+    private boolean plain;
+
     @Inject
-    private NodeMetricListCall call;
+    private NodeMetricSourceListCall call;
 
     @Inject
     private ConnectToClusterQuestion question;
@@ -46,10 +53,10 @@ public class NodeMetricListReplCommand extends BaseCommand 
implements Runnable {
     @Override
     public void run() {
         question.askQuestionIfNotConnected(nodeUrl.getNodeUrl())
-                .map(StringCallInput::new)
+                .map(UrlCallInput::new)
                 .then(Flows.fromCall(call))
                 .exceptionHandler(new 
ClusterNotInitializedExceptionHandler("Cannot list metrics", "cluster init"))
-                .print(new MetricListDecorator())
+                .print(new MetricSourceListDecorator(plain))
                 .start();
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceReplCommand.java
similarity index 73%
copy from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricCommand.java
copy to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceReplCommand.java
index 0a43305964..219b78a2e3 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricSourceReplCommand.java
@@ -20,13 +20,13 @@ package org.apache.ignite.internal.cli.commands.node.metric;
 import org.apache.ignite.internal.cli.commands.BaseCommand;
 import picocli.CommandLine.Command;
 
-/** Node metric command. */
-@Command(name = "metric",
+/** Node metric sources command in REPL. */
+@Command(name = "source",
         subcommands = {
-                NodeMetricEnableCommand.class,
-                NodeMetricDisableCommand.class,
-                NodeMetricListCommand.class
+                NodeMetricSourceEnableReplCommand.class,
+                NodeMetricSourceDisableReplCommand.class,
+                NodeMetricSourceListReplCommand.class
         },
-        description = "Node metric operations")
-public class NodeMetricCommand extends BaseCommand {
+        description = "Node metric sources operations")
+public class NodeMetricSourceReplCommand extends BaseCommand {
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
index f36ff6e84e..882440eba9 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlCommand.java
@@ -39,9 +39,7 @@ import 
org.apache.ignite.internal.cli.core.call.StringCallInput;
 import org.apache.ignite.internal.cli.core.exception.ExceptionWriter;
 import org.apache.ignite.internal.cli.core.exception.IgniteCliException;
 import 
org.apache.ignite.internal.cli.core.exception.handler.SqlExceptionHandler;
-import org.apache.ignite.internal.cli.decorators.PlainTableDecorator;
 import org.apache.ignite.internal.cli.decorators.SqlQueryResultDecorator;
-import org.apache.ignite.internal.cli.decorators.TableDecorator;
 import org.apache.ignite.internal.cli.sql.SqlManager;
 import picocli.CommandLine.ArgGroup;
 import picocli.CommandLine.Command;
@@ -86,12 +84,11 @@ public class SqlCommand extends BaseCommand implements 
Callable<Integer> {
     public Integer call() {
         try (SqlManager sqlManager = new SqlManager(jdbc)) {
             String executeCommand = execOptions.file != null ? 
extract(execOptions.file) : execOptions.command;
-            TableDecorator tableDecorator = plain ? new PlainTableDecorator() 
: new TableDecorator();
             return CallExecutionPipeline.builder(new SqlQueryCall(sqlManager))
                     .inputProvider(() -> new StringCallInput(executeCommand))
                     .output(spec.commandLine().getOut())
                     .errOutput(spec.commandLine().getErr())
-                    .decorator(new SqlQueryResultDecorator(tableDecorator))
+                    .decorator(new SqlQueryResultDecorator(plain))
                     .verbose(verbose)
                     .build().runPipeline();
         } catch (SQLException e) {
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
index 911760001a..3fcb800def 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/sql/SqlReplCommand.java
@@ -47,9 +47,7 @@ import org.apache.ignite.internal.cli.core.repl.Repl;
 import 
org.apache.ignite.internal.cli.core.repl.executor.RegistryCommandExecutor;
 import org.apache.ignite.internal.cli.core.repl.executor.ReplExecutorProvider;
 import org.apache.ignite.internal.cli.core.style.AnsiStringSupport.Color;
-import org.apache.ignite.internal.cli.decorators.PlainTableDecorator;
 import org.apache.ignite.internal.cli.decorators.SqlQueryResultDecorator;
-import org.apache.ignite.internal.cli.decorators.TableDecorator;
 import org.apache.ignite.internal.cli.sql.SqlManager;
 import org.apache.ignite.internal.cli.sql.SqlSchemaProvider;
 import org.jline.reader.impl.completer.AggregateCompleter;
@@ -128,12 +126,11 @@ public class SqlReplCommand extends BaseCommand 
implements Runnable {
     }
 
     private CallExecutionPipeline<?, ?> createSqlExecPipeline(SqlManager 
sqlManager, String line) {
-        TableDecorator tableDecorator = plain ? new PlainTableDecorator() : 
new TableDecorator();
         return CallExecutionPipeline.builder(new SqlQueryCall(sqlManager))
                 .inputProvider(() -> new StringCallInput(line))
                 .output(spec.commandLine().getOut())
                 .errOutput(spec.commandLine().getErr())
-                .decorator(new SqlQueryResultDecorator(tableDecorator))
+                .decorator(new SqlQueryResultDecorator(plain))
                 .verbose(verbose)
                 .build();
     }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterActivationPoint.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterActivationPoint.java
index 463b2b9f85..3f8792b3bb 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterActivationPoint.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/DynamicCompleterActivationPoint.java
@@ -26,6 +26,7 @@ import 
org.apache.ignite.internal.cli.core.repl.completer.filter.ExclusionsCompl
 import 
org.apache.ignite.internal.cli.core.repl.completer.hocon.ClusterConfigDynamicCompleterFactory;
 import 
org.apache.ignite.internal.cli.core.repl.completer.hocon.NodeConfigDynamicCompleterFactory;
 import 
org.apache.ignite.internal.cli.core.repl.completer.jdbc.JdbcUrlDynamicCompleterFactory;
+import 
org.apache.ignite.internal.cli.core.repl.completer.metric.MetricSourceDynamicCompleterFactory;
 import 
org.apache.ignite.internal.cli.core.repl.completer.node.NodeNameDynamicCompleterFactory;
 import 
org.apache.ignite.internal.cli.core.repl.completer.path.FilePathCompleter;
 import 
org.apache.ignite.internal.cli.core.repl.completer.unit.UnitIdDynamicCompleterFactory;
@@ -59,6 +60,9 @@ public class DynamicCompleterActivationPoint {
     @Inject
     private CliConfigDynamicCompleterFactory cliConfigDynamicCompleterFactory;
 
+    @Inject
+    private MetricSourceDynamicCompleterFactory 
metricSourceDynamicCompleterFactory;
+
 
     /**
      * Registers all dynamic completers in given {@link 
DynamicCompleterRegistry}.
@@ -164,5 +168,14 @@ public class DynamicCompleterActivationPoint {
                         .exclusiveEnableOptions().build(),
                 unitVersionsDynamicCompleterFactory
         );
+
+        registry.register(
+                CompleterConf.builder()
+                        .command("node", "metric", "source", "enable")
+                        .command("node", "metric", "source", "disable")
+                        .singlePositionalParameter()
+                        .build(),
+                metricSourceDynamicCompleterFactory
+        );
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/PlainTableDecorator.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/metric/MetricSourceDynamicCompleterFactory.java
similarity index 51%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/PlainTableDecorator.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/metric/MetricSourceDynamicCompleterFactory.java
index 99f8523269..5fe8ea0d65 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/PlainTableDecorator.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/completer/metric/MetricSourceDynamicCompleterFactory.java
@@ -15,25 +15,24 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.cli.decorators;
+package org.apache.ignite.internal.cli.core.repl.completer.metric;
 
-import org.apache.ignite.internal.cli.core.decorator.Decorator;
-import org.apache.ignite.internal.cli.core.decorator.TerminalOutput;
-import org.apache.ignite.internal.cli.sql.table.Table;
-import org.apache.ignite.internal.cli.util.PlainTableRenderer;
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleter;
+import 
org.apache.ignite.internal.cli.core.repl.completer.DynamicCompleterFactory;
+import 
org.apache.ignite.internal.cli.core.repl.completer.StringDynamicCompleter;
+import org.apache.ignite.internal.cli.core.repl.registry.MetricRegistry;
+
+/** Factory for metric source parameter completer. */
+@Singleton
+public class MetricSourceDynamicCompleterFactory implements 
DynamicCompleterFactory {
+
+    @Inject
+    private MetricRegistry registry;
 
-/**
- * Implementation of {@link Decorator} for {@link Table}.
- */
-public class PlainTableDecorator extends TableDecorator {
-    /**
-     * Transform {@link Table} to {@link TerminalOutput}.
-     *
-     * @param table incoming {@link Table}.
-     * @return Plain interpretation of {@link Table} in {@link TerminalOutput}.
-     */
     @Override
-    public TerminalOutput decorate(Table table) {
-        return () -> new PlainTableRenderer().render(table.header(), 
table.content());
+    public DynamicCompleter getDynamicCompleter(String[] words) {
+        return new StringDynamicCompleter(registry.metricSources());
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricCommand.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/registry/MetricRegistry.java
similarity index 62%
copy from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricCommand.java
copy to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/registry/MetricRegistry.java
index 0a43305964..546dbfa826 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/commands/node/metric/NodeMetricCommand.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/registry/MetricRegistry.java
@@ -15,18 +15,13 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.cli.commands.node.metric;
+package org.apache.ignite.internal.cli.core.repl.registry;
 
-import org.apache.ignite.internal.cli.commands.BaseCommand;
-import picocli.CommandLine.Command;
+import java.util.Set;
 
-/** Node metric command. */
-@Command(name = "metric",
-        subcommands = {
-                NodeMetricEnableCommand.class,
-                NodeMetricDisableCommand.class,
-                NodeMetricListCommand.class
-        },
-        description = "Node metric operations")
-public class NodeMetricCommand extends BaseCommand {
+/** Metric registry. */
+public interface MetricRegistry {
+
+    /** Metric sources. */
+    Set<String> metricSources();
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/registry/impl/MetricRegistryImpl.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/registry/impl/MetricRegistryImpl.java
new file mode 100644
index 0000000000..f194120215
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/core/repl/registry/impl/MetricRegistryImpl.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.cli.core.repl.registry.impl;
+
+import jakarta.inject.Inject;
+import jakarta.inject.Singleton;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import 
org.apache.ignite.internal.cli.call.node.metric.NodeMetricSourceListCall;
+import org.apache.ignite.internal.cli.core.call.UrlCallInput;
+import org.apache.ignite.internal.cli.core.repl.AsyncSessionEventListener;
+import org.apache.ignite.internal.cli.core.repl.SessionInfo;
+import org.apache.ignite.internal.cli.core.repl.registry.MetricRegistry;
+
+/** Implementation of {@link MetricRegistry}. */
+@Singleton
+public class MetricRegistryImpl implements MetricRegistry, 
AsyncSessionEventListener {
+
+    @Inject
+    private NodeMetricSourceListCall metricSourceListCall;
+
+    private final Set<String> metricSources = ConcurrentHashMap.newKeySet();
+
+    @Override
+    public Set<String> metricSources() {
+        return metricSources;
+    }
+
+    /**
+     * Gets list of metric sources from the node.
+     *
+     * @param sessionInfo sessionInfo.
+     */
+    @Override
+    public void onConnect(SessionInfo sessionInfo) {
+        CompletableFuture.runAsync(() -> {
+            try {
+                //TODO https://issues.apache.org/jira/browse/IGNITE-17416
+                metricSourceListCall.execute(new 
UrlCallInput(sessionInfo.nodeUrl()))
+                        .body()
+                        .forEach(source -> 
metricSources.add(source.getName()));
+            } catch (Exception ignored) {
+                // no-op
+            }
+        });
+    }
+
+    /**
+     * Clears metric sources list.
+     */
+    @Override
+    public void onDisconnect() {
+        metricSources.clear();
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/DefaultDecoratorRegistry.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/DefaultDecoratorRegistry.java
index a4d5a40d60..7317d3caee 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/DefaultDecoratorRegistry.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/DefaultDecoratorRegistry.java
@@ -38,8 +38,8 @@ public class DefaultDecoratorRegistry extends 
DecoratorRegistry {
         add(JsonString.class, new JsonDecorator());
         add(Profile.class, new ProfileDecorator());
         add(ProfileList.class, new ProfileListDecorator());
-        add(Table.class, new TableDecorator());
-        add(SqlQueryResult.class, new SqlQueryResultDecorator(new 
TableDecorator()));
+        add(Table.class, new TableDecorator(false));
+        add(SqlQueryResult.class, new SqlQueryResultDecorator(false));
         add(ClusterStatus.class, new ClusterStatusDecorator());
         add(NodeStatus.class, new NodeStatusDecorator());
     }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/MetricSetListDecorator.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/MetricSetListDecorator.java
new file mode 100644
index 0000000000..1444afdd0b
--- /dev/null
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/MetricSetListDecorator.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.cli.decorators;
+
+import com.jakewharton.fliptables.FlipTable;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.ignite.internal.cli.core.decorator.Decorator;
+import org.apache.ignite.internal.cli.core.decorator.TerminalOutput;
+import org.apache.ignite.internal.cli.util.PlainTableRenderer;
+import org.apache.ignite.rest.client.model.Metric;
+import org.apache.ignite.rest.client.model.MetricSet;
+
+/** Decorator for printing list of {@link MetricSet}. */
+public class MetricSetListDecorator implements Decorator<List<MetricSet>, 
TerminalOutput> {
+    private static final String[] HEADERS = {"Set name", "Metric name", 
"Description"};
+
+    private final boolean plain;
+
+    public MetricSetListDecorator(boolean plain) {
+        this.plain = plain;
+    }
+
+    @Override
+    public TerminalOutput decorate(List<MetricSet> data) {
+        if (plain) {
+            return () -> PlainTableRenderer.render(HEADERS, 
metricSetsToContent(data));
+        } else {
+            return () -> FlipTable.of(HEADERS, metricSetsToContent(data));
+        }
+    }
+
+    private static String[][] metricSetsToContent(List<MetricSet> data) {
+        List<String[]> result = new ArrayList<>();
+        for (MetricSet metricSet : data) {
+            result.add(new String[]{metricSet.getName(), "", ""});
+            for (Metric metric : metricSet.getMetrics()) {
+                String desc = metric.getDesc();
+                result.add(new String[]{"", metric.getName(), desc != null ? 
desc : ""});
+            }
+        }
+        return result.toArray(new String[0][]);
+    }
+}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/MetricListDecorator.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/MetricSourceListDecorator.java
similarity index 55%
rename from 
modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/MetricListDecorator.java
rename to 
modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/MetricSourceListDecorator.java
index 581022f38c..82259e80ac 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/MetricListDecorator.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/MetricSourceListDecorator.java
@@ -17,29 +17,38 @@
 
 package org.apache.ignite.internal.cli.decorators;
 
+import com.jakewharton.fliptables.FlipTable;
 import java.util.List;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 import org.apache.ignite.internal.cli.core.decorator.Decorator;
 import org.apache.ignite.internal.cli.core.decorator.TerminalOutput;
+import org.apache.ignite.internal.cli.util.PlainTableRenderer;
 import org.apache.ignite.rest.client.model.MetricSource;
 
 /** Decorator for printing list of {@link MetricSource}. */
-public class MetricListDecorator implements Decorator<List<MetricSource>, 
TerminalOutput> {
+public class MetricSourceListDecorator implements 
Decorator<List<MetricSource>, TerminalOutput> {
+    private static final String[] HEADERS = {"Set name", "Enabled"};
+
+    private final boolean plain;
+
+    public MetricSourceListDecorator(boolean plain) {
+        this.plain = plain;
+    }
+
     @Override
     public TerminalOutput decorate(List<MetricSource> data) {
-        return () -> {
-            String enabled = data.stream()
-                    .filter(MetricSource::getEnabled)
-                    .map(MetricSource::getName)
-                    .collect(Collectors.joining(System.lineSeparator()));
-            String disabled = data.stream()
-                    .filter(metricSource -> !metricSource.getEnabled())
-                    .map(MetricSource::getName)
-                    .collect(Collectors.joining(System.lineSeparator()));
-            return Stream.of("Enabled metric sources:", enabled, "Disabled 
metric sources:", disabled)
-                    .filter(s -> !s.isEmpty())
-                    .collect(Collectors.joining(System.lineSeparator()));
-        };
+        if (plain) {
+            return () -> PlainTableRenderer.render(HEADERS, 
metricSourcesToContent(data));
+        } else {
+            return () -> FlipTable.of(HEADERS, metricSourcesToContent(data));
+        }
+    }
+
+    private static String[][] metricSourcesToContent(List<MetricSource> data) {
+        return data.stream()
+                .map(metricSource -> new String[]{
+                        metricSource.getName(),
+                        metricSource.getEnabled() ? "enabled" : "disabled"
+                })
+                .toArray(String[][]::new);
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/PlainTopologyDecorator.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/PlainTopologyDecorator.java
deleted file mode 100644
index 38a14719ec..0000000000
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/PlainTopologyDecorator.java
+++ /dev/null
@@ -1,42 +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.cli.decorators;
-
-import java.util.List;
-import org.apache.ignite.internal.cli.core.decorator.Decorator;
-import org.apache.ignite.internal.cli.core.decorator.TerminalOutput;
-import org.apache.ignite.internal.cli.util.PlainTableRenderer;
-import org.apache.ignite.rest.client.model.ClusterNode;
-
-/**
- * Implementation of {@link Decorator} for the list of {@link ClusterNode}.
- */
-public class PlainTopologyDecorator extends TopologyDecorator {
-    /**
-     * Transform list of {@link ClusterNode} to {@link TerminalOutput}.
-     *
-     * @param topology incoming list of {@link ClusterNode}.
-     * @return Plain interpretation of list of {@link ClusterNode} in {@link 
TerminalOutput}.
-     */
-
-    @Override
-    public TerminalOutput decorate(List<ClusterNode> topology) {
-        String[][] content = topologyToContent(topology);
-        return () -> new PlainTableRenderer().render(HEADERS, content);
-    }
-}
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/SqlQueryResultDecorator.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/SqlQueryResultDecorator.java
index 261c516de2..ad2b53f757 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/SqlQueryResultDecorator.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/SqlQueryResultDecorator.java
@@ -29,8 +29,8 @@ public class SqlQueryResultDecorator implements 
Decorator<SqlQueryResult, Termin
 
     private final DefaultDecorator<String> messageDecorator = new 
DefaultDecorator<>();
 
-    public SqlQueryResultDecorator(TableDecorator tableDecorator) {
-        this.tableDecorator = tableDecorator;
+    public SqlQueryResultDecorator(boolean plain) {
+        this.tableDecorator = new TableDecorator(plain);
     }
 
     @Override
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/TableDecorator.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/TableDecorator.java
index 3743278afc..14e59d4a64 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/TableDecorator.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/TableDecorator.java
@@ -21,11 +21,18 @@ import com.jakewharton.fliptables.FlipTableConverters;
 import org.apache.ignite.internal.cli.core.decorator.Decorator;
 import org.apache.ignite.internal.cli.core.decorator.TerminalOutput;
 import org.apache.ignite.internal.cli.sql.table.Table;
+import org.apache.ignite.internal.cli.util.PlainTableRenderer;
 
 /**
  * Implementation of {@link Decorator} for {@link Table}.
  */
 public class TableDecorator implements Decorator<Table, TerminalOutput> {
+    private final boolean plain;
+
+    public TableDecorator(boolean plain) {
+        this.plain = plain;
+    }
+
     /**
      * Transform {@link Table} to {@link TerminalOutput}.
      *
@@ -34,6 +41,10 @@ public class TableDecorator implements Decorator<Table, 
TerminalOutput> {
      */
     @Override
     public TerminalOutput decorate(Table table) {
-        return () -> FlipTableConverters.fromObjects(table.header(), 
table.content());
+        if (plain) {
+            return () -> PlainTableRenderer.render(table.header(), 
table.content());
+        } else {
+            return () -> FlipTableConverters.fromObjects(table.header(), 
table.content());
+        }
     }
 }
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/TopologyDecorator.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/TopologyDecorator.java
index c1aa44bb50..066cb4954a 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/TopologyDecorator.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/decorators/TopologyDecorator.java
@@ -21,6 +21,7 @@ import com.jakewharton.fliptables.FlipTable;
 import java.util.List;
 import org.apache.ignite.internal.cli.core.decorator.Decorator;
 import org.apache.ignite.internal.cli.core.decorator.TerminalOutput;
+import org.apache.ignite.internal.cli.util.PlainTableRenderer;
 import org.apache.ignite.rest.client.model.ClusterNode;
 
 /**
@@ -28,7 +29,13 @@ import org.apache.ignite.rest.client.model.ClusterNode;
  */
 public class TopologyDecorator implements Decorator<List<ClusterNode>, 
TerminalOutput> {
     /** List of headers to decorate topology. */
-    protected static final String[] HEADERS = {"name", "host", "port", 
"consistent id", "id"};
+    private static final String[] HEADERS = {"name", "host", "port", 
"consistent id", "id"};
+
+    private final boolean plain;
+
+    public TopologyDecorator(boolean plain) {
+        this.plain = plain;
+    }
 
     /**
      * Transform list of {@link ClusterNode} to {@link TerminalOutput}.
@@ -38,10 +45,14 @@ public class TopologyDecorator implements 
Decorator<List<ClusterNode>, TerminalO
      */
     @Override
     public TerminalOutput decorate(List<ClusterNode> topology) {
-        return () -> FlipTable.of(HEADERS, topologyToContent(topology));
+        if (plain) {
+            return () -> PlainTableRenderer.render(HEADERS, 
topologyToContent(topology));
+        } else {
+            return () -> FlipTable.of(HEADERS, topologyToContent(topology));
+        }
     }
 
-    protected String[][] topologyToContent(List<ClusterNode> topology) {
+    private static String[][] topologyToContent(List<ClusterNode> topology) {
         return topology.stream().map(
             node -> new String[]{
                 node.getName(),
diff --git 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/util/PlainTableRenderer.java
 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/util/PlainTableRenderer.java
index a4753cb672..4a88ca994e 100644
--- 
a/modules/cli/src/main/java/org/apache/ignite/internal/cli/util/PlainTableRenderer.java
+++ 
b/modules/cli/src/main/java/org/apache/ignite/internal/cli/util/PlainTableRenderer.java
@@ -37,7 +37,7 @@ public class PlainTableRenderer {
      * @param content header of table.
      * @return Plain interpretation.
      */
-    public String render(String[] hdr, Object[][] content) {
+    public static String render(String[] hdr, Object[][] content) {
         StringJoiner sj = new StringJoiner(LINE_SEPARATOR);
         sj.add(tableRowToString(hdr));
         for (Object[] row : content) {
@@ -52,7 +52,7 @@ public class PlainTableRenderer {
      * @param row row of table.
      * @return Plain interpretation of row.
      */
-    private String tableRowToString(Object[] row) {
+    private static String tableRowToString(Object[] row) {
         return 
Stream.of(row).map(String::valueOf).collect(Collectors.joining(COLUMN_DELIMITER));
     }
 }
diff --git 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/IgniteCliInterfaceTest.java
 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/IgniteCliInterfaceTest.java
index 64e56f900f..75a406a25f 100644
--- 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/IgniteCliInterfaceTest.java
+++ 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/IgniteCliInterfaceTest.java
@@ -154,7 +154,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest 
{
         @DisplayName("metric")
         class Metric {
             @Test
-            @DisplayName("metric enable srcName")
+            @DisplayName("metric source enable srcName")
             void enable() {
                 clientAndServer
                         .when(request()
@@ -164,7 +164,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest 
{
                         )
                         .respond(response(null));
 
-                int exitCode = execute("node metric enable --node-url " + 
mockUrl + " srcName");
+                int exitCode = execute("node metric source enable --node-url " 
+ mockUrl + " srcName");
 
                 assertThatExitCodeMeansSuccess(exitCode);
 
@@ -173,7 +173,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest 
{
             }
 
             @Test
-            @DisplayName("metric disable srcName")
+            @DisplayName("metric source disable srcName")
             void disable() {
                 clientAndServer
                         .when(request()
@@ -183,7 +183,7 @@ public class IgniteCliInterfaceTest extends AbstractCliTest 
{
                         )
                         .respond(response(null));
 
-                int exitCode = execute("node metric disable --node-url " + 
mockUrl + " srcName");
+                int exitCode = execute("node metric source disable --node-url 
" + mockUrl + " srcName");
 
                 assertThatExitCodeMeansSuccess(exitCode);
 
@@ -192,21 +192,40 @@ public class IgniteCliInterfaceTest extends 
AbstractCliTest {
             }
 
             @Test
-            @DisplayName("metric list")
-            void list() {
+            @DisplayName("metric source list")
+            void listSources() {
                 String responseBody = 
"[{\"name\":\"enabledMetric\",\"enabled\":true},{\"name\":\"disabledMetric\",\"enabled\":false}]";
                 clientAndServer
                         .when(request()
                                 .withMethod("GET")
-                                .withPath("/management/v1/metric/node")
+                                .withPath("/management/v1/metric/node/source")
+                        )
+                        .respond(response(responseBody));
+
+                int exitCode = execute("node metric source list --plain 
--node-url " + mockUrl);
+
+                assertThatExitCodeMeansSuccess(exitCode);
+
+                assertOutputEqual("Set 
name\tEnabled\nenabledMetric\tenabled\ndisabledMetric\tdisabled\n");
+                assertThatStderrIsEmpty();
+            }
+
+            @Test
+            @DisplayName("metric list")
+            void listSets() {
+                String responseBody = 
"[{\"name\":\"metricSet\",\"metrics\":[{\"name\":\"metric\",\"desc\":\"description\"}]}]";
+                clientAndServer
+                        .when(request()
+                                .withMethod("GET")
+                                .withPath("/management/v1/metric/node/set")
                         )
                         .respond(response(responseBody));
 
-                int exitCode = execute("node metric list --node-url " + 
mockUrl);
+                int exitCode = execute("node metric list --plain --node-url " 
+ mockUrl);
 
                 assertThatExitCodeMeansSuccess(exitCode);
 
-                assertOutputEqual("Enabled metric 
sources:\nenabledMetric\nDisabled metric sources:\ndisabledMetric\n");
+                assertOutputEqual("Set name\tMetric 
name\tDescription\nmetricSet\t\t\n\tmetric\tdescription");
                 assertThatStderrIsEmpty();
             }
         }
diff --git 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/UrlOptionsNegativeTest.java
 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/UrlOptionsNegativeTest.java
index 6ef3a715d4..6c9bd3b2ad 100644
--- 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/UrlOptionsNegativeTest.java
+++ 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/commands/UrlOptionsNegativeTest.java
@@ -49,12 +49,14 @@ import 
org.apache.ignite.internal.cli.commands.node.config.NodeConfigShowCommand
 import 
org.apache.ignite.internal.cli.commands.node.config.NodeConfigShowReplCommand;
 import 
org.apache.ignite.internal.cli.commands.node.config.NodeConfigUpdateCommand;
 import 
org.apache.ignite.internal.cli.commands.node.config.NodeConfigUpdateReplCommand;
-import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricDisableCommand;
-import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricDisableReplCommand;
-import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricEnableCommand;
-import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricEnableReplCommand;
-import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricListCommand;
-import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricListReplCommand;
+import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricSetListCommand;
+import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricSetListReplCommand;
+import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricSourceDisableCommand;
+import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricSourceDisableReplCommand;
+import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricSourceEnableCommand;
+import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricSourceEnableReplCommand;
+import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricSourceListCommand;
+import 
org.apache.ignite.internal.cli.commands.node.metric.NodeMetricSourceListReplCommand;
 import org.apache.ignite.internal.cli.commands.node.status.NodeStatusCommand;
 import 
org.apache.ignite.internal.cli.commands.node.status.NodeStatusReplCommand;
 import org.apache.ignite.internal.cli.config.ini.IniConfigManager;
@@ -123,9 +125,10 @@ public class UrlOptionsNegativeTest {
                 arguments(ClusterConfigShowCommand.class, CLUSTER_URL_OPTION, 
List.of()),
                 arguments(ClusterConfigUpdateCommand.class, 
CLUSTER_URL_OPTION, List.of("{key: value}")),
                 arguments(ClusterStatusCommand.class, CLUSTER_URL_OPTION, 
List.of()),
-                arguments(NodeMetricEnableCommand.class, NODE_URL_OPTION, 
List.of("srcName")),
-                arguments(NodeMetricDisableCommand.class, NODE_URL_OPTION, 
List.of("srcName")),
-                arguments(NodeMetricListCommand.class, NODE_URL_OPTION, 
List.of()),
+                arguments(NodeMetricSourceEnableCommand.class, 
NODE_URL_OPTION, List.of("srcName")),
+                arguments(NodeMetricSourceDisableCommand.class, 
NODE_URL_OPTION, List.of("srcName")),
+                arguments(NodeMetricSourceListCommand.class, NODE_URL_OPTION, 
List.of()),
+                arguments(NodeMetricSetListCommand.class, NODE_URL_OPTION, 
List.of()),
                 arguments(LogicalTopologyCommand.class, CLUSTER_URL_OPTION, 
List.of()),
                 arguments(PhysicalTopologyCommand.class, CLUSTER_URL_OPTION, 
List.of()),
                 arguments(ClusterInitCommand.class, CLUSTER_URL_OPTION, 
List.of("--cluster-name=cluster", "--meta-storage-node=test"))
@@ -142,9 +145,10 @@ public class UrlOptionsNegativeTest {
                 arguments(ClusterConfigShowReplCommand.class, 
CLUSTER_URL_OPTION, List.of()),
                 arguments(ClusterConfigUpdateReplCommand.class, 
CLUSTER_URL_OPTION, List.of("{key: value}")),
                 arguments(ClusterStatusReplCommand.class, CLUSTER_URL_OPTION, 
List.of()),
-                arguments(NodeMetricEnableReplCommand.class, NODE_URL_OPTION, 
List.of("srcName")),
-                arguments(NodeMetricDisableReplCommand.class, NODE_URL_OPTION, 
List.of("srcName")),
-                arguments(NodeMetricListReplCommand.class, NODE_URL_OPTION, 
List.of()),
+                arguments(NodeMetricSourceEnableReplCommand.class, 
NODE_URL_OPTION, List.of("srcName")),
+                arguments(NodeMetricSourceDisableReplCommand.class, 
NODE_URL_OPTION, List.of("srcName")),
+                arguments(NodeMetricSourceListReplCommand.class, 
NODE_URL_OPTION, List.of()),
+                arguments(NodeMetricSetListReplCommand.class, NODE_URL_OPTION, 
List.of()),
                 arguments(LogicalTopologyReplCommand.class, 
CLUSTER_URL_OPTION, List.of()),
                 arguments(PhysicalTopologyReplCommand.class, 
CLUSTER_URL_OPTION, List.of()),
                 arguments(ClusterInitReplCommand.class, CLUSTER_URL_OPTION, 
List.of("--cluster-name=cluster", "--meta-storage-node=test")),
diff --git 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/util/PlainTableRendererTest.java
 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/util/PlainTableRendererTest.java
index 5705e00d57..e023e43a30 100644
--- 
a/modules/cli/src/test/java/org/apache/ignite/internal/cli/util/PlainTableRendererTest.java
+++ 
b/modules/cli/src/test/java/org/apache/ignite/internal/cli/util/PlainTableRendererTest.java
@@ -26,11 +26,11 @@ class PlainTableRendererTest {
 
     @Test
     void testRender() {
-        String[] header = new String[]{"id", "name", "address"};
-        Object[] row1 = new Object[]{1, "John", null};
-        Object[] row2 = new Object[]{2, "Jessica", "any address"};
-        Object[][] content = new Object[][]{row1, row2};
-        String render = new PlainTableRenderer().render(header, content);
+        String[] header = {"id", "name", "address"};
+        Object[] row1 = {1, "John", null};
+        Object[] row2 = {2, "Jessica", "any address"};
+        Object[][] content = {row1, row2};
+        String render = PlainTableRenderer.render(header, content);
         String[] renderedRows = render.split(System.lineSeparator());
         assertAll(
                 () -> assertEquals(3, renderedRows.length),
diff --git a/modules/rest-api/openapi/openapi.yaml 
b/modules/rest-api/openapi/openapi.yaml
index 27df9b5fab..53cd30ff45 100644
--- a/modules/rest-api/openapi/openapi.yaml
+++ b/modules/rest-api/openapi/openapi.yaml
@@ -322,12 +322,12 @@ paths:
     get:
       tags:
       - deployment
-      description: All units statutes.
+      description: All units statuses.
       operationId: units
       parameters: []
       responses:
         "200":
-          description: All statutes returned successfully.
+          description: All statuses returned successfully.
           content:
             application/json:
               schema:
@@ -402,7 +402,7 @@ paths:
           type: string
       responses:
         "200":
-          description: All statutes returned successfully.
+          description: All statuses returned successfully.
           content:
             application/json:
               schema:
@@ -556,28 +556,6 @@ paths:
             application/problem+json:
               schema:
                 $ref: '#/components/schemas/Problem'
-  /management/v1/metric/node:
-    get:
-      tags:
-      - nodeMetric
-      description: Provides a list of all available metric sources.
-      operationId: listNodeMetrics
-      parameters: []
-      responses:
-        "200":
-          description: Returned a list of metric sources.
-          content:
-            application/json:
-              schema:
-                type: array
-                items:
-                  $ref: '#/components/schemas/MetricSource'
-        "500":
-          description: Internal error.
-          content:
-            application/problem+json:
-              schema:
-                $ref: '#/components/schemas/Problem'
   /management/v1/metric/node/disable:
     post:
       tags:
@@ -634,6 +612,50 @@ paths:
             application/problem+json:
               schema:
                 $ref: '#/components/schemas/Problem'
+  /management/v1/metric/node/set:
+    get:
+      tags:
+      - nodeMetric
+      description: Provides a list of all enabled metric sets.
+      operationId: listNodeMetricSets
+      parameters: []
+      responses:
+        "200":
+          description: Returned a list of metric sets.
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/MetricSet'
+        "500":
+          description: Internal error
+          content:
+            application/problem+json:
+              schema:
+                $ref: '#/components/schemas/Problem'
+  /management/v1/metric/node/source:
+    get:
+      tags:
+      - nodeMetric
+      description: Provides a list of all available metric sources.
+      operationId: listNodeMetricSources
+      parameters: []
+      responses:
+        "200":
+          description: Returned a list of metric sources.
+          content:
+            application/json:
+              schema:
+                type: array
+                items:
+                  $ref: '#/components/schemas/MetricSource'
+        "500":
+          description: Internal error.
+          content:
+            application/problem+json:
+              schema:
+                $ref: '#/components/schemas/Problem'
   /management/v1/node/state:
     get:
       tags:
@@ -787,13 +809,52 @@ components:
           type: string
           description: The issue with the parameter.
       description: Information about invalid request parameter.
+    Metric:
+      required:
+      - name
+      type: object
+      properties:
+        name:
+          type: string
+          description: Returns the metric name.
+        desc:
+          type: string
+          description: Returns the metric description.
+          nullable: true
+      description: REST representation of Metric.
+    MetricSet:
+      required:
+      - metrics
+      - name
+      type: object
+      properties:
+        name:
+          required:
+          - "true"
+          type: string
+          description: Metric set name.
+        metrics:
+          required:
+          - "true"
+          type: array
+          description: List of metrics.
+          items:
+            $ref: '#/components/schemas/Metric'
+      description: REST representation of MetricSet.
     MetricSource:
+      required:
+      - enabled
+      - name
       type: object
       properties:
         name:
+          required:
+          - "true"
           type: string
           description: Metric source name.
         enabled:
+          required:
+          - "true"
           type: boolean
           description: "If True, the metric is tracked. Otherwise, the metric 
is not\
             \ tracked."
diff --git 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/deployment/DeploymentCodeApi.java
 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/deployment/DeploymentCodeApi.java
index 4c18eec4b4..80d40554c4 100644
--- 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/deployment/DeploymentCodeApi.java
+++ 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/deployment/DeploymentCodeApi.java
@@ -126,9 +126,9 @@ public interface DeploymentCodeApi {
     /**
      * All units status REST method.
      */
-    @Operation(operationId = "units", description = "All units statutes.")
+    @Operation(operationId = "units", description = "All units statuses.")
     @ApiResponse(responseCode = "200",
-            description = "All statutes returned successfully.",
+            description = "All statuses returned successfully.",
             content = @Content(mediaType = APPLICATION_JSON, array = 
@ArraySchema(schema = @Schema(implementation = UnitStatusDto.class)))
     )
     @ApiResponse(responseCode = "500",
@@ -196,7 +196,7 @@ public interface DeploymentCodeApi {
      */
     @Operation(operationId = "byConsistentId", description = "Status of units 
which deployed on node.")
     @ApiResponse(responseCode = "200",
-            description = "All statutes returned successfully.",
+            description = "All statuses returned successfully.",
             content = @Content(mediaType = APPLICATION_JSON, array = 
@ArraySchema(schema = @Schema(implementation = UnitStatusDto.class)))
     )
     @ApiResponse(responseCode = "500",
diff --git 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSourceDto.java
 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricDto.java
similarity index 60%
copy from 
modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSourceDto.java
copy to 
modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricDto.java
index 0198cd7460..7d86cd9673 100644
--- 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSourceDto.java
+++ 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricDto.java
@@ -21,38 +21,38 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonGetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
+import org.jetbrains.annotations.Nullable;
 
 /**
- * REST representation of MetricSource.
+ * REST representation of Metric.
  */
-@Schema(name = "MetricSource", description = "Metric sources provided by 
modules.")
-public class MetricSourceDto {
-    /** Name of the metric source. */
-    @Schema(description = "Metric source name.")
+@Schema(name = "Metric")
+public class MetricDto {
+    /** Metric name. */
     private final String name;
 
-    /** Enabled. */
-    @Schema(description = "If True, the metric is tracked. Otherwise, the 
metric is not tracked.")
-    private final boolean enabled;
+    /** Metric description. */
+    private final String desc;
 
     /**
      * Constructor.
      *
-     * @param name metric source name
-     * @param enabled flags showing whether this metric source is enabled or 
not
+     * @param name metric name
+     * @param desc metric description
      */
     @JsonCreator
-    public MetricSourceDto(
+    public MetricDto(
             @JsonProperty("name") String name,
-            @JsonProperty("enabled") boolean enabled) {
+            @JsonProperty("desc") @Nullable String desc
+    ) {
         this.name = name;
-        this.enabled = enabled;
+        this.desc = desc;
     }
 
     /**
-     * Returns the metric source name.
+     * Returns the metric name.
      *
-     * @return metric source name
+     * @return metric name
      */
     @JsonGetter("name")
     public String name() {
@@ -60,12 +60,12 @@ public class MetricSourceDto {
     }
 
     /**
-     * Returns the status of the metric source.
+     * Returns the metric description.
      *
-     * @return {@code true} if metrics are enabled, otherwise - {@code false}
+     * @return metric description
      */
-    @JsonGetter("enabled")
-    public boolean enabled() {
-        return enabled;
+    @JsonGetter("desc")
+    public @Nullable String desc() {
+        return desc;
     }
 }
diff --git 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSourceDto.java
 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSetDto.java
similarity index 60%
copy from 
modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSourceDto.java
copy to 
modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSetDto.java
index 0198cd7460..691a5cb1d1 100644
--- 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSourceDto.java
+++ 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSetDto.java
@@ -21,38 +21,40 @@ import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonGetter;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
+import java.util.Collection;
 
 /**
- * REST representation of MetricSource.
+ * REST representation of MetricSet.
  */
-@Schema(name = "MetricSource", description = "Metric sources provided by 
modules.")
-public class MetricSourceDto {
-    /** Name of the metric source. */
-    @Schema(description = "Metric source name.")
+@Schema(name = "MetricSet")
+public class MetricSetDto {
+    /** Metric set name. */
+    @Schema(description = "Metric set name.", required = true)
     private final String name;
 
-    /** Enabled. */
-    @Schema(description = "If True, the metric is tracked. Otherwise, the 
metric is not tracked.")
-    private final boolean enabled;
+    /** Metrics. */
+    @Schema(description = "List of metrics.", required = true)
+    private final Collection<MetricDto> metrics;
 
     /**
      * Constructor.
      *
-     * @param name metric source name
-     * @param enabled flags showing whether this metric source is enabled or 
not
+     * @param name metric name
+     * @param metrics metrics
      */
     @JsonCreator
-    public MetricSourceDto(
+    public MetricSetDto(
             @JsonProperty("name") String name,
-            @JsonProperty("enabled") boolean enabled) {
+            @JsonProperty("metrics") Collection<MetricDto> metrics
+    ) {
         this.name = name;
-        this.enabled = enabled;
+        this.metrics = metrics;
     }
 
     /**
-     * Returns the metric source name.
+     * Returns the metric name.
      *
-     * @return metric source name
+     * @return metric name
      */
     @JsonGetter("name")
     public String name() {
@@ -60,12 +62,12 @@ public class MetricSourceDto {
     }
 
     /**
-     * Returns the status of the metric source.
+     * Returns metrics.
      *
-     * @return {@code true} if metrics are enabled, otherwise - {@code false}
+     * @return metrics
      */
-    @JsonGetter("enabled")
-    public boolean enabled() {
-        return enabled;
+    @JsonGetter("metrics")
+    public Collection<MetricDto> metrics() {
+        return metrics;
     }
 }
diff --git 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSourceDto.java
 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSourceDto.java
index 0198cd7460..d0088ef047 100644
--- 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSourceDto.java
+++ 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/MetricSourceDto.java
@@ -28,11 +28,11 @@ import io.swagger.v3.oas.annotations.media.Schema;
 @Schema(name = "MetricSource", description = "Metric sources provided by 
modules.")
 public class MetricSourceDto {
     /** Name of the metric source. */
-    @Schema(description = "Metric source name.")
+    @Schema(description = "Metric source name.", required = true)
     private final String name;
 
     /** Enabled. */
-    @Schema(description = "If True, the metric is tracked. Otherwise, the 
metric is not tracked.")
+    @Schema(description = "If True, the metric is tracked. Otherwise, the 
metric is not tracked.", required = true)
     private final boolean enabled;
 
     /**
diff --git 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/NodeMetricApi.java
 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/NodeMetricApi.java
index dd1e740ed5..3ccf12b913 100644
--- 
a/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/NodeMetricApi.java
+++ 
b/modules/rest-api/src/main/java/org/apache/ignite/internal/rest/api/metric/NodeMetricApi.java
@@ -65,11 +65,19 @@ public interface NodeMetricApi {
     void disable(@Body String srcName);
 
     /** List metric sources. */
-    @Operation(operationId = "listNodeMetrics", description = "Provides a list 
of all available metric sources.")
+    @Operation(operationId = "listNodeMetricSources", description = "Provides 
a list of all available metric sources.")
     @ApiResponse(responseCode = "200", description = "Returned a list of 
metric sources.")
     @ApiResponse(responseCode = "500", description = "Internal error.",
             content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = 
@Schema(implementation = Problem.class)))
     @Produces(MediaType.APPLICATION_JSON)
-    @Get()
-    Collection<MetricSourceDto> list();
+    @Get("source")
+    Collection<MetricSourceDto> listMetricSources();
+
+    /** List metric sets. */
+    @Operation(operationId = "listNodeMetricSets", description = "Provides a 
list of all enabled metric sets.")
+    @ApiResponse(responseCode = "200", description = "Returned a list of 
metric sets.")
+    @ApiResponse(responseCode = "500", description = "Internal error",
+            content = @Content(mediaType = MediaType.PROBLEM_JSON, schema = 
@Schema(implementation = Problem.class)))
+    @Get("set")
+    Collection<MetricSetDto> listMetricSets();
 }
diff --git 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/metrics/NodeMetricController.java
 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/metrics/NodeMetricController.java
index af4fd32444..c366e763fd 100644
--- 
a/modules/rest/src/main/java/org/apache/ignite/internal/rest/metrics/NodeMetricController.java
+++ 
b/modules/rest/src/main/java/org/apache/ignite/internal/rest/metrics/NodeMetricController.java
@@ -19,8 +19,12 @@ package org.apache.ignite.internal.rest.metrics;
 
 import io.micronaut.http.annotation.Controller;
 import java.util.Collection;
+import java.util.List;
 import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
 import org.apache.ignite.internal.metrics.MetricManager;
+import org.apache.ignite.internal.rest.api.metric.MetricDto;
+import org.apache.ignite.internal.rest.api.metric.MetricSetDto;
 import org.apache.ignite.internal.rest.api.metric.MetricSourceDto;
 import org.apache.ignite.internal.rest.api.metric.NodeMetricApi;
 import 
org.apache.ignite.internal.rest.metrics.exception.MetricNotFoundException;
@@ -53,9 +57,21 @@ public class NodeMetricController implements NodeMetricApi {
     }
 
     @Override
-    public Collection<MetricSourceDto> list() {
+    public Collection<MetricSourceDto> listMetricSources() {
         return metricManager.metricSources().stream()
                 .map(source -> new MetricSourceDto(source.name(), 
source.enabled()))
                 .collect(Collectors.toList());
     }
+
+    @Override
+    public Collection<MetricSetDto> listMetricSets() {
+        return metricManager.metricSnapshot().get1().values().stream()
+                .map(metricSet -> {
+                    List<MetricDto> metricDtos = 
StreamSupport.stream(metricSet.spliterator(), false)
+                            .map(metric -> new MetricDto(metric.name(), 
metric.description()))
+                            .collect(Collectors.toList());
+                    return new MetricSetDto(metricSet.name(), metricDtos);
+                })
+                .collect(Collectors.toList());
+    }
 }

Reply via email to