Merge branch 'main' into ignite-17354
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/FilteringIterator.java b/modules/core/src/main/java/org/apache/ignite/internal/util/FilteringIterator.java
similarity index 97%
rename from modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/FilteringIterator.java
rename to modules/core/src/main/java/org/apache/ignite/internal/util/FilteringIterator.java
index 1ec865d..0baa62e 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/FilteringIterator.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/FilteringIterator.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.sql.engine.util;
+package org.apache.ignite.internal.util;
 
 import java.util.Iterator;
 import java.util.NoSuchElementException;
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TransformingIterator.java b/modules/core/src/main/java/org/apache/ignite/internal/util/TransformingIterator.java
similarity index 97%
rename from modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TransformingIterator.java
rename to modules/core/src/main/java/org/apache/ignite/internal/util/TransformingIterator.java
index d8efccb..827e62b 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/TransformingIterator.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/TransformingIterator.java
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-package org.apache.ignite.internal.sql.engine.util;
+package org.apache.ignite.internal.util;
 
 import java.util.Iterator;
 import java.util.function.Function;
diff --git a/modules/metrics/pom.xml b/modules/metrics/pom.xml
new file mode 100644
index 0000000..720e99c
--- /dev/null
+++ b/modules/metrics/pom.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.ignite</groupId>
+        <artifactId>ignite-parent</artifactId>
+        <version>1</version>
+        <relativePath>../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>ignite-metrics</artifactId>
+    <version>3.0.0-SNAPSHOT</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-core</artifactId>
+        </dependency>
+
+        <!-- 3rd party dependencies -->
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+        </dependency>
+
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.hamcrest</groupId>
+            <artifactId>hamcrest</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-api</artifactId>
+            <scope>test</scope>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter-engine</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AbstractMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AbstractMetric.java
new file mode 100644
index 0000000..bd8623d
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AbstractMetric.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metrics;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Abstract metric.
+ */
+public abstract class AbstractMetric implements Metric {
+    /** Metric name. It is local for a particular {@link MetricSet}. */
+    private final String name;
+
+    /** Metric description. */
+    @Nullable
+    private final String desc;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     */
+    public AbstractMetric(String name, @Nullable String desc) {
+        assert name != null;
+        assert !name.isEmpty();
+
+        this.name = name;
+        this.desc = desc;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String name() {
+        return name;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    @Nullable
+    public String description() {
+        return desc;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+
+        AbstractMetric metric = (AbstractMetric) o;
+
+        if (!name.equals(metric.name)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int hashCode() {
+        return name.hashCode();
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AbstractMetricSource.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AbstractMetricSource.java
new file mode 100644
index 0000000..a5081c1
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AbstractMetricSource.java
@@ -0,0 +1,137 @@
+/*
+ * 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.metrics;
+
+import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
+
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+/**
+ * Base class for all metric sources.
+ *
+ * @param <T> Holder type.
+ */
+public abstract class AbstractMetricSource<T extends AbstractMetricSource.Holder<T>> implements MetricSource {
+    /** Holder field updater. */
+    @SuppressWarnings("rawtypes")
+    private static final AtomicReferenceFieldUpdater<AbstractMetricSource, Holder> HOLDER_FIELD_UPD =
+            newUpdater(AbstractMetricSource.class, AbstractMetricSource.Holder.class, "holder");
+
+    /** Metric source name. */
+    private final String name;
+
+    /** Metric instances holder. */
+    private volatile T holder;
+
+    /**
+     * Base constructor for all metric source implementations.
+     *
+     * @param name Metric source name.
+     */
+    protected AbstractMetricSource(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Returns metric source name.
+     *
+     * @return Metric source name.
+     */
+    @Override public final String name() {
+        return name;
+    }
+
+    /**
+     * Checks whether metrics is enabled (switched on) or not (switched off) for metric source.
+     *
+     * @return {@code True} if metrics are enabled, otherwise - {@code false}.
+     */
+    @Override public final boolean enabled() {
+        return holder != null;
+    }
+
+    /**
+     * Returns metric instances' holder. Use this on order to avoid metric lookup from map-like data structures.
+     * Returned value is {@code null} if metrics are disabled.
+     *
+     * @return Metrics holder instance if metrics are enabled, otherwise - {@code null}.
+     */
+    public final T holder() {
+        return holder;
+    }
+
+    /**
+     * Method is responsible for creation of appropriate holder instance in underlying implementations.
+     *
+     * @return New instance of metrics holder that must implements {@link Holder} interface.
+     */
+    protected abstract T createHolder();
+
+    /** {@inheritDoc} */
+    @Override public final MetricSet enable() {
+        MetricSetBuilder bldr = new MetricSetBuilder(name);
+
+        T hldr = createHolder();
+
+        init(bldr, hldr);
+
+        MetricSet metricSet = bldr.build();
+
+        return HOLDER_FIELD_UPD.compareAndSet(this, null, hldr) ? metricSet : null;
+    }
+
+    /** {@inheritDoc} */
+    @Override public final void disable() {
+        T holder0 = holder;
+
+        if (HOLDER_FIELD_UPD.compareAndSet(this, holder0, null)) {
+            cleanup(holder0);
+        }
+    }
+
+    /**
+     * Method is responsible for:
+     * <ol>
+     *     <li>Creation of {@link MetricSet} instance using provided {@link MetricSetBuilder}.</li>
+     *     <li>Creation of metric instances in given holder.</li>
+     *     <li>Other initialization if needed.</li>
+     * </ol>.
+     *
+     * @param bldr Metric registry builder.
+     * @param holder Metric instances' holder.
+     */
+    protected abstract void init(MetricSetBuilder bldr, T holder);
+
+    /**
+     * Method is responsible for cleanup and release of all resources initialized or created during {@link #init} method
+     * execution. Note that {@link MetricSet} and {@link Holder} instances will be released automatically.
+     *
+     * @param holder Metric instances holder.
+     */
+    protected void cleanup(T holder) {
+        // No-op.
+    }
+
+    /**
+     * Marker interface for metric instances holder.
+     *
+     * @param <T> Holder type subclass.
+     */
+    protected interface Holder<T extends Holder<T>> {
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AtomicDoubleMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AtomicDoubleMetric.java
new file mode 100644
index 0000000..5931351
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AtomicDoubleMetric.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metrics;
+
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+
+/**
+ * Double metric based on atomic updater of double value.
+ */
+public class AtomicDoubleMetric extends AbstractMetric implements DoubleMetric {
+    /** Field updater. */
+    private static final AtomicLongFieldUpdater<AtomicDoubleMetric> updater =
+            AtomicLongFieldUpdater.newUpdater(AtomicDoubleMetric.class, "val");
+
+    /** Value. */
+    private volatile long val;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     */
+    public AtomicDoubleMetric(String name, String desc) {
+        super(name, desc);
+    }
+
+    /**
+     * Adds given value to the metric value.
+     *
+     * @param v Value to be added.
+     */
+    public void add(double v) {
+        for (;;) {
+            long exp = val;
+            long upd = Double.doubleToLongBits(Double.longBitsToDouble(exp) + v);
+            if (updater.compareAndSet(this, exp, upd)) {
+                break;
+            }
+        }
+    }
+
+    /**
+     * Sets value.
+     *
+     * @param val Value.
+     */
+    public void value(double val) {
+        this.val = Double.doubleToLongBits(val);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double value() {
+        return Double.longBitsToDouble(val);
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AtomicIntMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AtomicIntMetric.java
new file mode 100644
index 0000000..7e38221
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AtomicIntMetric.java
@@ -0,0 +1,76 @@
+/*
+ * 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.metrics;
+
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Integer metric is implemented as a volatile {@code int} value.
+ */
+public class AtomicIntMetric extends AbstractMetric implements IntMetric {
+    /** Field updater. */
+    private static final AtomicIntegerFieldUpdater<AtomicIntMetric> updater =
+            AtomicIntegerFieldUpdater.newUpdater(AtomicIntMetric.class, "val");
+
+    /** Value. */
+    private volatile int val;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     */
+    public AtomicIntMetric(String name, @Nullable String desc) {
+        super(name, desc);
+    }
+
+    /**
+     * Adds x to the metric.
+     *
+     * @param x Value to be added.
+     */
+    public void add(int x) {
+        updater.addAndGet(this, x);
+    }
+
+    /** Increment the metric. */
+    public void increment() {
+        updater.incrementAndGet(this);
+    }
+
+    /** Decrement the metric. */
+    public void decrement() {
+        updater.decrementAndGet(this);
+    }
+
+    /**
+     * Sets value.
+     *
+     * @param val Value.
+     */
+    public void value(int val) {
+        this.val = val;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int value() {
+        return val;
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AtomicLongMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AtomicLongMetric.java
new file mode 100644
index 0000000..5d13eb7
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/AtomicLongMetric.java
@@ -0,0 +1,76 @@
+/*
+ * 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.metrics;
+
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Long metric implementation.
+ */
+public class AtomicLongMetric extends AbstractMetric implements LongMetric {
+    /** Field updater. */
+    static final AtomicLongFieldUpdater<AtomicLongMetric> updater =
+            AtomicLongFieldUpdater.newUpdater(AtomicLongMetric.class, "val");
+
+    /** Field value. */
+    private volatile long val;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     */
+    public AtomicLongMetric(String name, @Nullable String desc) {
+        super(name, desc);
+    }
+
+    /**
+     * Adds x to the metric.
+     *
+     * @param x Value to be added.
+     */
+    public void add(long x) {
+        updater.getAndAdd(this, x);
+    }
+
+    /** Adds 1 to the metric. */
+    public void increment() {
+        updater.incrementAndGet(this);
+    }
+
+    /** Adds -1 to the metric. */
+    public void decrement() {
+        updater.decrementAndGet(this);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long value() {
+        return val;
+    }
+
+    /**
+     * Sets value.
+     *
+     * @param val Value.
+     */
+    public void value(long val) {
+        this.val = val;
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/CompositeAwareIterator.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/CompositeAwareIterator.java
new file mode 100644
index 0000000..6c84c98
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/CompositeAwareIterator.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metrics;
+
+import java.util.Iterator;
+
+/**
+ * The iterator that considers composite metrics as a group of scalar ones, see {@link CompositeMetric#asScalarMetrics()},
+ * and enumerates composite metrics in order to enumerate every scalar metric.
+ */
+public class CompositeAwareIterator implements Iterator<Metric> {
+    /** Delegate iterator of metrics, some of them may be composite. */
+    private final Iterator<Metric> delegate;
+
+    /** Iterator for single composite metric, iterating over the group of scalar ones. */
+    private Iterator<Metric> compositeMetricIterator = null;
+
+    /**
+     * The constructor.
+     *
+     * @param delegate Collection of metrics that can contain composite metrics.
+     */
+    public CompositeAwareIterator(Iterator<Metric> delegate) {
+        this.delegate = delegate;
+    }
+
+    /** {@inheritDoc} */
+    @Override public boolean hasNext() {
+        if (compositeMetricIterator == null) {
+            return delegate.hasNext();
+        } else if (compositeMetricIterator.hasNext()) {
+            return true;
+        } else {
+            compositeMetricIterator = null;
+
+            return delegate.hasNext();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override public Metric next() {
+        if (compositeMetricIterator == null) {
+            return nextCompositeAware();
+        } else if (compositeMetricIterator.hasNext()) {
+            return compositeMetricIterator.next();
+        } else {
+            compositeMetricIterator = null;
+
+            return nextCompositeAware();
+        }
+    }
+
+    /**
+     * Next method that is aware of composite metrics.
+     *
+     * @return Next value.
+     */
+    private Metric nextCompositeAware() {
+        Metric nextValue = delegate.next();
+
+        if (nextValue instanceof CompositeMetric) {
+            compositeMetricIterator = ((CompositeMetric) nextValue).asScalarMetrics().iterator();
+
+            return compositeMetricIterator.next();
+        } else {
+            return nextValue;
+        }
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/CompositeMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/CompositeMetric.java
new file mode 100644
index 0000000..91458c0
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/CompositeMetric.java
@@ -0,0 +1,32 @@
+/*
+ * 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.metrics;
+
+import java.util.List;
+
+/**
+ * A composite metric is a group of closely related values. The values themselves are numeric.
+ */
+public interface CompositeMetric extends Metric {
+    /**
+     * Get a group of values composing this metric as a list of scalar metrics.
+     *
+     * @return List of scalar metrics.
+     */
+    List<Metric> asScalarMetrics();
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DistributionMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DistributionMetric.java
new file mode 100644
index 0000000..0cd87c5
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DistributionMetric.java
@@ -0,0 +1,194 @@
+/*
+ * 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.metrics;
+
+import static java.util.Collections.unmodifiableList;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLongArray;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Distribution metric calculates counts of measurements that gets into each bounds interval.
+ * Note, that {@link #value()} will return array length of {@code bounds.length + 1}.
+ * Last element contains count of measurements bigger than most right value of bounds.
+ */
+public class DistributionMetric extends AbstractMetric implements CompositeMetric {
+    /** Updater that atomically updates {@link #scalarMetrics} field. */
+    private static final AtomicReferenceFieldUpdater<DistributionMetric, List> scalarMetricsUpdater =
+            AtomicReferenceFieldUpdater.newUpdater(DistributionMetric.class, List.class, "scalarMetrics");
+
+    /** Distribution metric first interval low bound. */
+    public static final long FIRST_LOW_BOUND = 0;
+
+    /** Distribution metric first interval low bound, string representation. */
+    public static final String FIRST_LOW_BOUND_STRING = "0";
+
+    /** Distribution metric last interval high bound. */
+    public static final String INF = "inf";
+
+    /** Distribution range divider. */
+    public static final char RANGE_DIVIDER = '_';
+
+    /** Distribution bucket divider. */
+    public static final String BUCKET_DIVIDER = ", ";
+
+    /** Distribution metric name and value divider. */
+    public static final char METRIC_DIVIDER = ':';
+
+    /** Count of measurement for each bound. */
+    private final AtomicLongArray measurements;
+
+    /** Bounds of measurements. */
+    private final long[] bounds;
+
+    /** List of scalar metrics. */
+    private volatile List<Metric> scalarMetrics = null;
+
+    /**
+     * The constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     * @param bounds Bounds of the buckets. The array must be sorted and its elements must be unique. The 0-th bound must be
+     *               greater or equal to {@code 0}.
+     */
+    public DistributionMetric(String name, @Nullable String desc, long[] bounds) {
+        super(name, desc);
+
+        assert bounds != null && bounds.length > 0;
+        assert bounds[0] >= FIRST_LOW_BOUND;
+        assert isSortedAndUnique(bounds);
+
+        this.bounds = bounds;
+        this.measurements = new AtomicLongArray(bounds.length + 1);
+    }
+
+    /**
+     * Check whether given array is sorted and its elements are unique.
+     *
+     * @param arr Array to check.
+     * @return {@code True} if array sorted and its elements are unique, {@code false} otherwise.
+     */
+    private static boolean isSortedAndUnique(long[] arr) {
+        if (arr.length < 2) {
+            return true;
+        }
+
+        for (int i = 1; i < arr.length; i++) {
+            if (arr[i - 1] >= arr[i]) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Adds a value to the interval which the value belongs to.
+     *
+     * @param x Value.
+     */
+    public void add(long x) {
+        assert x >= 0;
+
+        //Expect arrays of few elements.
+        for (int i = 0; i < bounds.length; i++) {
+            if (x <= bounds[i]) {
+                measurements.incrementAndGet(i);
+
+                return;
+            }
+        }
+
+        measurements.incrementAndGet(bounds.length);
+    }
+
+    /** {@inheritDoc} */
+    public long[] value() {
+        long[] res = new long[measurements.length()];
+
+        for (int i = 0; i < measurements.length(); i++) {
+            res[i] = measurements.get(i);
+        }
+
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getValueAsString() {
+        StringBuilder sb = new StringBuilder("[");
+
+        List<Metric> scalarMetrics = asScalarMetrics();
+
+        for (int i = 0; i < scalarMetrics.size(); i++) {
+            LongMetric m = (LongMetric) scalarMetrics.get(i);
+
+            sb.append(m.name())
+                    .append(METRIC_DIVIDER)
+                    .append(m.value());
+
+            if (i < scalarMetrics.size() - 1) {
+                sb.append(BUCKET_DIVIDER);
+            }
+        }
+
+        sb.append(']');
+
+        return sb.toString();
+    }
+
+    /**
+     * Bounds of the buckets of distribution.
+     *
+     * @return Bounds of the buckets of distribution.
+     */
+    public long[] bounds() {
+        return bounds;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<Metric> asScalarMetrics() {
+        if (scalarMetrics == null) {
+            List<Metric> metrics = new ArrayList<>();
+
+            String from = FIRST_LOW_BOUND_STRING;
+
+            for (int i = 0; i < measurements.length(); i++) {
+                String to = i == measurements.length() - 1 ? INF : String.valueOf(bounds[i]);
+
+                String name = name() + RANGE_DIVIDER + from + RANGE_DIVIDER + to;
+
+                final int index = i;
+                LongGauge gauge = new LongGauge(name, "Single distribution bucket", () -> measurements.get(index));
+
+                metrics.add(gauge);
+
+                from = to;
+            }
+
+            scalarMetricsUpdater.compareAndSet(this, null, unmodifiableList(metrics));
+        }
+
+        return scalarMetrics;
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DoubleAdderMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DoubleAdderMetric.java
new file mode 100644
index 0000000..d67105d
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DoubleAdderMetric.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metrics;
+
+import java.util.concurrent.atomic.DoubleAdder;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Double metric based on {@link DoubleAdder}.
+ */
+public class DoubleAdderMetric extends AbstractMetric implements DoubleMetric {
+    /** Value. */
+    private volatile DoubleAdder val;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     */
+    public DoubleAdderMetric(String name, @Nullable String desc) {
+        super(name, desc);
+
+        this.val = new DoubleAdder();
+    }
+
+    /**
+     * Adds x to the metric.
+     *
+     * @param x Value to be added.
+     */
+    public void add(double x) {
+        val.add(x);
+    }
+
+    /**
+     * Sets value.
+     *
+     * @param val Value.
+     */
+    public void value(double val) {
+        DoubleAdder adder = new DoubleAdder();
+        adder.add(val);
+        this.val = adder;
+    }
+
+    /** {@inheritDoc} */
+    @Override public double value() {
+        return val.sum();
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DoubleGauge.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DoubleGauge.java
new file mode 100644
index 0000000..bd555e6
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DoubleGauge.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics;
+
+import java.util.function.DoubleSupplier;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Double metric based on {@link DoubleSupplier}.
+ */
+public class DoubleGauge extends AbstractMetric implements DoubleMetric {
+    /** Value supplier. */
+    private final DoubleSupplier val;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     * @param val Supplier.
+     */
+    public DoubleGauge(String name, @Nullable String desc, DoubleSupplier val) {
+        super(name, desc);
+
+        this.val = val;
+    }
+
+    /** {@inheritDoc} */
+    @Override public double value() {
+        return val.getAsDouble();
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DoubleMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DoubleMetric.java
new file mode 100644
index 0000000..1c7e550
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/DoubleMetric.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metrics;
+
+/**
+ * Basic interface for double metric.
+ */
+public interface DoubleMetric extends Metric {
+    /**
+     * Value of the metric.
+     *
+     * @return Value of the metric.
+     */
+    double value();
+
+    /** {@inheritDoc} */
+    @Override
+    default String getValueAsString() {
+        return Double.toString(value());
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/HitRateMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/HitRateMetric.java
new file mode 100644
index 0000000..93a5604
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/HitRateMetric.java
@@ -0,0 +1,195 @@
+/*
+ * 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.metrics;
+
+import java.util.concurrent.atomic.AtomicLongArray;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Accumulates approximate hit rate statistics.
+ * Calculates number of hits in last {@code rateTimeInterval} milliseconds.
+ * Algorithm is based on circular array of {@code size} hit counters, each is responsible for last corresponding time
+ * interval of {@code rateTimeInterval}/{@code size} milliseconds. Resulting number of hits is sum of all counters.
+ *
+ * <p>Implementation is nonblocking and protected from hits loss.
+ * Maximum relative error is 1/{@code size}.
+ * 2^55 - 1 hits per interval can be accumulated without numeric overflow.
+ */
+public class HitRateMetric extends AbstractMetric implements LongMetric {
+    /** Default counters array size. */
+    public static final int DFLT_SIZE = 10;
+
+    /** Bits that store actual hit count. */
+    private static final int TAG_OFFSET = 56;
+
+    /** Useful part mask. */
+    private static final long NO_TAG_MASK = ~(-1L << TAG_OFFSET);
+
+    /** Time interval when hits are counted to calculate rate, in milliseconds. */
+    private final long rateTimeInterval;
+
+    /** Counters array size. */
+    private final int size;
+
+    /** Tagged counters. */
+    private final AtomicLongArray taggedCounters;
+
+    /** Last hit times. */
+    private final AtomicLongArray lastHitTimes;
+
+    /**
+     * The constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     * @param rateTimeInterval Rate time interval in milliseconds.
+     */
+    public HitRateMetric(String name, @Nullable String desc, long rateTimeInterval) {
+        this(name, desc, rateTimeInterval, DFLT_SIZE);
+    }
+
+    /**
+     * The constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     * @param rateTimeInterval Rate time interval in milliseconds.
+     * @param size Counters array size.
+     */
+    public HitRateMetric(String name, @Nullable String desc, long rateTimeInterval, int size) {
+        super(name, desc);
+
+        assert rateTimeInterval > 0 : "rateTimeInterval should be positive";
+        assert size > 1 : "Minimum value for size is 2";
+
+        this.rateTimeInterval = rateTimeInterval;
+        this.size = size;
+        taggedCounters = new AtomicLongArray(size);
+        lastHitTimes = new AtomicLongArray(size);
+    }
+
+    /**
+     * Adds the given count for hits to the metric.
+     *
+     * @param hits Count of hits.
+     */
+    public void add(long hits) {
+        long curTs = System.currentTimeMillis();
+
+        int curPos = position(curTs);
+
+        clearIfObsolete(curTs, curPos);
+
+        lastHitTimes.set(curPos, curTs);
+
+        // Order is important. Hit won't be cleared by concurrent #clearIfObsolete.
+        taggedCounters.addAndGet(curPos, hits);
+    }
+
+    /** Adds 1 to the metric. */
+    public void increment() {
+        add(1);
+    }
+
+    /** {@inheritDoc} */
+    @Override public long value() {
+        long curTs = System.currentTimeMillis();
+
+        long sum = 0;
+
+        for (int i = 0; i < size; i++) {
+            clearIfObsolete(curTs, i);
+
+            sum += untag(taggedCounters.get(i));
+        }
+
+        return sum;
+    }
+
+    /**
+     * Clear specific counter if obsolete.
+     *
+     * @param curTs Current timestamp.
+     * @param i Index.
+     */
+    private void clearIfObsolete(long curTs, int i) {
+        long cur = taggedCounters.get(i);
+
+        byte curTag = getTag(cur);
+
+        long lastTs = lastHitTimes.get(i);
+
+        if (isObsolete(curTs, lastTs)) {
+            if (taggedCounters.compareAndSet(i, cur, taggedLongZero(++curTag))) { // ABA problem prevention.
+                lastHitTimes.set(i, curTs);
+            }
+            // If CAS failed, counter is reset by another thread.
+        }
+    }
+
+    /**
+     * Whether the last hit time was too long ago.
+     *
+     * @param curTs Current timestamp.
+     * @param lastHitTime Last hit timestamp.
+     * @return True, is last hit time was too long ago.
+     */
+    private boolean isObsolete(long curTs, long lastHitTime) {
+        return curTs - lastHitTime > rateTimeInterval * (size - 1) / size;
+    }
+
+    /**
+     * Index of counter for given timestamp.
+     *
+     * @param time Timestamp.
+     * @return Index of counter for given timestamp.
+     */
+    private int position(long time) {
+        return (int) ((time % rateTimeInterval * size) / rateTimeInterval);
+    }
+
+    /**
+     * Create a zero value with tag byte.
+     *
+     * @param tag Tag byte.
+     * @return 0L with given tag byte.
+     */
+    private static long taggedLongZero(byte tag) {
+        return ((long) tag << TAG_OFFSET);
+    }
+
+    /**
+     * Long without tag byte.
+     *
+     * @param l Tagged long.
+     * @return Long without tag byte.
+     */
+    private static long untag(long l) {
+        return l & NO_TAG_MASK;
+    }
+
+    /**
+     * Tag byte.
+     *
+     * @param taggedLong Tagged long.
+     * @return Tag byte.
+     */
+    private static byte getTag(long taggedLong) {
+        return (byte) (taggedLong >> TAG_OFFSET);
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/IntGauge.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/IntGauge.java
new file mode 100644
index 0000000..99e8c7b
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/IntGauge.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics;
+
+import java.util.function.IntSupplier;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Implementation based on primitive supplier.
+ */
+public class IntGauge extends AbstractMetric implements IntMetric {
+    /** Value supplier. */
+    private final IntSupplier val;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     * @param val Supplier.
+     */
+    public IntGauge(String name, @Nullable String desc, IntSupplier val) {
+        super(name, desc);
+
+        this.val = val;
+    }
+
+    /** {@inheritDoc} */
+    @Override public int value() {
+        return val.getAsInt();
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/IntMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/IntMetric.java
new file mode 100644
index 0000000..3c69686
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/IntMetric.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metrics;
+
+/**
+ * Interface for the metrics that holds int primitive.
+ */
+public interface IntMetric extends Metric {
+    /**
+     * Value of the metric.
+     *
+     * @return Value of the metric.
+     */
+    int value();
+
+    /** {@inheritDoc} */
+    @Override
+    default String getValueAsString() {
+        return Integer.toString(value());
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/LongAdderMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/LongAdderMetric.java
new file mode 100644
index 0000000..3f0f180
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/LongAdderMetric.java
@@ -0,0 +1,81 @@
+/*
+ * 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.metrics;
+
+import java.util.concurrent.atomic.LongAdder;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Long metric based on {@link LongAdder}.
+ */
+public class LongAdderMetric extends AbstractMetric implements LongMetric {
+    /** Value. */
+    private volatile LongAdder val;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     */
+    public LongAdderMetric(String name, @Nullable String desc) {
+        super(name, desc);
+
+        this.val = new LongAdder();
+    }
+
+    /**
+     * Adds x to the metric.
+     *
+     * @param x Value to be added.
+     */
+    public void add(long x) {
+        val.add(x);
+    }
+
+    /**
+     * Increment the metric.
+     */
+    public void increment() {
+        val.increment();
+    }
+
+    /**
+     * Decrement the metric.
+     */
+    public void decrement() {
+        val.decrement();
+    }
+
+    /**
+     * Sets value.
+     *
+     * @param val Value.
+     */
+    public void value(long val) {
+        LongAdder adder = new LongAdder();
+        adder.add(val);
+        this.val = adder;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long value() {
+        return val.sum();
+    }
+
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/LongGauge.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/LongGauge.java
new file mode 100644
index 0000000..299f471
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/LongGauge.java
@@ -0,0 +1,47 @@
+/*
+ * 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.metrics;
+
+import java.util.function.LongSupplier;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Implementation based on primitive supplier.
+ */
+public class LongGauge extends AbstractMetric implements LongMetric {
+    /** Value supplier. */
+    private final LongSupplier val;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     * @param desc Description.
+     * @param val Supplier.
+     */
+    public LongGauge(String name, @Nullable String desc, LongSupplier val) {
+        super(name, desc);
+
+        this.val = val;
+    }
+
+    /** {@inheritDoc} */
+    @Override public long value() {
+        return val.getAsLong();
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/LongMetric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/LongMetric.java
new file mode 100644
index 0000000..bb2eeeb
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/LongMetric.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metrics;
+
+/**
+ * Interface for the metrics that holds long primitive.
+ */
+public interface LongMetric extends Metric {
+    /**
+     * Value of the metric.
+     *
+     * @return Value of the metric.
+     */
+    long value();
+
+    /** {@inheritDoc} */
+    @Override
+    default String getValueAsString() {
+        return Long.toString(value());
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/Metric.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/Metric.java
new file mode 100644
index 0000000..a3ad48d
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/Metric.java
@@ -0,0 +1,48 @@
+/*
+ * 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.metrics;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Basic metric interface.
+ */
+public interface Metric {
+    /**
+     * Name of the metric.
+     *
+     * @return Name of the metric.
+     */
+    String name();
+
+    /**
+     * Description of the metric.
+     *
+     * @return Description of the metric.
+     */
+    @Nullable
+    String description();
+
+    /**
+     * String representation of metric value.
+     *
+     * @return String representation of metric value.
+     */
+    @Nullable
+    String getValueAsString();
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricManager.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricManager.java
new file mode 100644
index 0000000..91cc669
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricManager.java
@@ -0,0 +1,126 @@
+/*
+ * 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.metrics;
+
+import java.util.Map;
+import org.apache.ignite.internal.manager.IgniteComponent;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Metric manager.
+ */
+public class MetricManager implements IgniteComponent {
+    /**
+     * Metric registry.
+     */
+    private final MetricRegistry registry;
+
+    /**
+     * Constructor.
+     */
+    public MetricManager() {
+        this.registry = new MetricRegistry();
+    }
+
+    /** {@inheritDoc} */
+    @Override public void start() {
+        // No-op.
+    }
+
+    /** {@inheritDoc} */
+    @Override public void stop() throws Exception {
+        // No-op.
+    }
+
+    /**
+     * Register metric source. See {@link MetricRegistry#registerSource(MetricSource)}.
+     *
+     * @param src Metric source.
+     */
+    public void registerSource(MetricSource src) {
+        registry.registerSource(src);
+    }
+
+    /**
+     * Unregister metric source. See {@link MetricRegistry#unregisterSource(MetricSource)}.
+     *
+     * @param src Metric source.
+     */
+    public void unregisterSource(MetricSource src) {
+        registry.unregisterSource(src);
+    }
+
+    /**
+     * Unregister metric source by name. See {@link MetricRegistry#unregisterSource(String)}.
+     *
+     * @param srcName Metric source name.
+     */
+    public void unregisterSource(String srcName) {
+        registry.unregisterSource(srcName);
+    }
+
+    /**
+     * Enable metric source. See {@link MetricRegistry#enable(MetricSource)}.
+     *
+     * @param src Metric source.
+     * @return Metric set, or {@code null} if already enabled.
+     */
+    public MetricSet enable(MetricSource src) {
+        return registry.enable(src);
+    }
+
+    /**
+     * Enable metric source by name. See {@link MetricRegistry#enable(String)}.
+     *
+     * @param srcName Source name.
+     * @return Metric set, or {@code null} if already enabled.
+     */
+    public MetricSet enable(final String srcName) {
+        return registry.enable(srcName);
+    }
+
+    /**
+     * Disable metric source. See {@link MetricRegistry#disable(MetricSource)}.
+     *
+     * @param src Metric source.
+     */
+    public void disable(MetricSource src) {
+        registry.disable(src);
+    }
+
+    /**
+     * Disable metric source by name. See {@link MetricRegistry#disable(String)}.
+     *
+     * @param srcName Source name.
+     */
+    public void disable(final String srcName) {
+        registry.disable(srcName);
+    }
+
+    /**
+     * Metrics snapshot. This is a snapshot of metric sets with corresponding version, the values of the metrics in the
+     * metric sets that are included into the snapshot, are changed dynamically.
+     *
+     * @return Metrics snapshot.
+     */
+    @NotNull
+    public IgniteBiTuple<Map<String, MetricSet>, Long> metricSnapshot() {
+        return registry.metricSnapshot();
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricRegistry.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricRegistry.java
new file mode 100644
index 0000000..d0b8e19
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricRegistry.java
@@ -0,0 +1,295 @@
+/*
+ * 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.metrics;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.unmodifiableMap;
+import static java.util.Objects.requireNonNull;
+import static java.util.concurrent.atomic.AtomicReferenceFieldUpdater.newUpdater;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Metric registry. Metrics source (see {@link MetricSource} must be registered in this metrics registry after initialization
+ * of corresponding component and must be unregistered in case of component is destroyed or stopped. Metrics registry also
+ * provides access to all enabled metrics through corresponding metrics sets. Metrics registry lifetime is equal to the node lifetime.
+ */
+public class MetricRegistry {
+    private static final AtomicReferenceFieldUpdater<MetricRegistry, IgniteBiTuple> metricSnapshotUpdater =
+            newUpdater(MetricRegistry.class, IgniteBiTuple.class, "metricSnapshot");
+
+    private final Lock lock = new ReentrantLock();
+
+    /** Registered metric sources. */
+    private final Map<String, MetricSource> sources = new HashMap<>();
+
+    /**
+     * Metrics snapshot. This is a snapshot of metric sets with corresponding version, the values of the metrics in the
+     * metric sets that are included into the snapshot, are changed dynamically.
+     */
+    private volatile IgniteBiTuple<Map<String, MetricSet>, Long> metricSnapshot = new IgniteBiTuple<>(emptyMap(), 0L);
+
+    /**
+     * Register metric source. It must be registered in this metrics registry after initialization of corresponding component
+     * and must be unregistered in case of component is destroyed or stopped, see {@link #unregisterSource(MetricSource)}.
+     * By registering, the metric source isn't enabled implicitly.
+     *
+     * @param src Metric source.
+     * @throws IllegalStateException If metric source with the given name already exists.
+     */
+    public void registerSource(MetricSource src) {
+        lock.lock();
+
+        try {
+            // Metric source shouldn't be enabled before because the second call of MetricSource#enable will return null.
+            assert !src.enabled() : "Metric source shouldn't be enabled before registration in registry.";
+
+            MetricSource old = sources.putIfAbsent(src.name(), src);
+
+            if (old != null) {
+                throw new IllegalStateException("Metrics source with given name already exists: " + src.name());
+            }
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Unregister metric source. It must be unregistered in case of corresponding component is destroyed or stopped.
+     * Metric source is also disabled while unregistered, see {@link #disable(String)}.
+     *
+     * @param src Metric source.
+     * @throws IllegalStateException If the given metric source isn't registered.
+     */
+    public void unregisterSource(MetricSource src) {
+        unregisterSource(src.name());
+    }
+
+    /**
+     * Unregister metric source. It must be unregistered in case of corresponding component is destroyed or stopped.
+     * Metric source is also disabled while unregistered, see {@link #disable(String)}.
+     *
+     * @param srcName Metric source name.
+     * @throws IllegalStateException If the metric source with the given name isn't registered.
+     */
+    public void unregisterSource(String srcName) {
+        lock.lock();
+
+        try {
+            disable(srcName);
+
+            sources.remove(srcName);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Enable metric set for the given metric source.
+     *
+     * @param src Metric source.
+     * @return Metric set, or {@code null} if the metric set is already enabled.
+     * @throws IllegalStateException If metric source isn't registered.
+     * @throws IllegalArgumentException If metric source isn't the same as registered.
+     */
+    public MetricSet enable(@NotNull MetricSource src) {
+        lock.lock();
+
+        try {
+            MetricSource registered = checkAndGetRegistered(src);
+
+            MetricSet metricSet = registered.enable();
+
+            if (metricSet != null) {
+                addMetricSet(src.name(), metricSet);
+            }
+
+            return metricSet;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Enable metric set for the given metric source.
+     *
+     * @param srcName Metric source name.
+     * @return Metric set, or {@code null} if the metric set is already enabled.
+     * @throws IllegalStateException If metric source with the given name doesn't exist.
+     */
+    public MetricSet enable(final String srcName) {
+        lock.lock();
+
+        try {
+            MetricSource src = sources.get(srcName);
+
+            if (src == null) {
+                throw new IllegalStateException("Metrics source with given name doesn't exist: " + srcName);
+            }
+
+            MetricSet metricSet = src.enable();
+
+            if (metricSet != null) {
+                addMetricSet(src.name(), metricSet);
+            }
+
+            return metricSet;
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Disable metric set for the given metric source.
+     *
+     * @param src Metric source.
+     * @throws IllegalStateException If metric source isn't registered.
+     * @throws IllegalArgumentException If metric source isn't the same as registered.
+     */
+    public void disable(@NotNull MetricSource src) {
+        lock.lock();
+
+        try {
+            MetricSource registered = checkAndGetRegistered(src);
+
+            if (!registered.enabled()) {
+                return;
+            }
+
+            registered.disable();
+
+            removeMetricSet(registered.name());
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Disable metric set for the given metric source.
+     *
+     * @param srcName Metric source name.
+     * @throws IllegalStateException If metric source with given name doesn't exist.
+     */
+    public void disable(final String srcName) {
+        lock.lock();
+
+        try {
+            MetricSource src = sources.get(srcName);
+
+            if (src == null) {
+                throw new IllegalStateException("Metrics source with given name doesn't exists: " + srcName);
+            }
+
+            if (!src.enabled()) {
+                return;
+            }
+
+            src.disable();
+
+            removeMetricSet(srcName);
+        } finally {
+            lock.unlock();
+        }
+    }
+
+    /**
+     * Check that the given metric source is registered. This method should be called under the {@link MetricRegistry#lock}.
+     *
+     * @param src Metric source.
+     * @return Registered metric source.
+     * @throws IllegalStateException If metric source isn't registered.
+     * @throws IllegalArgumentException If metric source isn't the same as registered.
+     */
+    @NotNull
+    private MetricSource checkAndGetRegistered(@NotNull MetricSource src) {
+        requireNonNull(src);
+
+        MetricSource registered = sources.get(src.name());
+
+        if (registered == null) {
+            throw new IllegalStateException("Metrics source isn't registered: " + src.name());
+        }
+
+        if (!src.equals(registered)) {
+            throw new IllegalArgumentException("Given metric source is not the same as registered by the same name: " + src.name());
+        }
+
+        return registered;
+    }
+
+    /**
+     * Add metric set to {@link MetricRegistry#metricSnapshot}. This creates new version of metric snapshot. This method should be
+     * called under the {@link MetricRegistry#lock}.
+     *
+     * @param srcName Metric source name.
+     * @param metricSet Metric set.
+     */
+    private void addMetricSet(String srcName, MetricSet metricSet) {
+        Map<String, MetricSet> metricSets = new TreeMap<>(metricSnapshot.get1());
+
+        metricSets.put(srcName, metricSet);
+
+        updateMetricSnapshot(metricSets);
+    }
+
+    /**
+     * Removes metric set from {@link MetricRegistry#metricSnapshot}. This creates new version of metric snapshot. This method should be
+     * called under the {@link MetricRegistry#lock}.
+     *
+     * @param srcName Metric source name.
+     */
+    private void removeMetricSet(String srcName) {
+        Map<String, MetricSet> metricSets = new TreeMap<>(metricSnapshot.get1());
+
+        metricSets.remove(srcName);
+
+        updateMetricSnapshot(metricSets);
+    }
+
+    /**
+     * Create a new version of {@link MetricRegistry#metricSnapshot}. This method should be called under the {@link MetricRegistry#lock}.
+     *
+     * @param metricSets New map of metric sets that should be saved to new version of metric snapshot.
+     */
+    private void updateMetricSnapshot(Map<String, MetricSet> metricSets) {
+        IgniteBiTuple<Map<String, MetricSet>, Long> old = metricSnapshot;
+
+        long newVersion = old.get2() + 1;
+
+        IgniteBiTuple<Map<String, MetricSet>, Long> newMetricSnapshot = new IgniteBiTuple<>(unmodifiableMap(metricSets), newVersion);
+
+        metricSnapshotUpdater.compareAndSet(this, old, newMetricSnapshot);
+    }
+
+    /**
+     * Metrics snapshot. This is a snapshot of metric sets with corresponding version, the values of the metrics in the
+     * metric sets that are included into the snapshot, are changed dynamically.
+     *
+     * @return Metrics snapshot.
+     */
+    public IgniteBiTuple<Map<String, MetricSet>, Long> metricSnapshot() {
+        return metricSnapshot;
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricSet.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricSet.java
new file mode 100644
index 0000000..92c7ad1
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricSet.java
@@ -0,0 +1,73 @@
+/*
+ * 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.metrics;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+
+/**
+ * The Metric set that consists of set of metrics identified by a metric name.
+ * The metrics set is immutable.
+ */
+public class MetricSet implements Iterable<Metric> {
+    /** Metrics set name. */
+    private final String name;
+
+    /** Registered metrics. */
+    private final Map<String, Metric> metrics;
+
+    /**
+     * Creates an instance of a metrics set with given name and metrics.
+     *
+     * @param name Metrics set name.
+     * @param metrics Metrics.
+     */
+    public MetricSet(String name, Map<String, Metric> metrics) {
+        this.name = name;
+        this.metrics = Collections.unmodifiableMap(metrics);
+    }
+
+    /**
+     * Get metric by name.
+     *
+     * @param name Metric name.
+     * @return Metric.
+     */
+    @Nullable
+    @TestOnly
+    public <M extends Metric> M get(String name) {
+        return (M) metrics.get(name);
+    }
+
+    /** {@inheritDoc} */
+    public Iterator<Metric> iterator() {
+        return metrics.values().iterator();
+    }
+
+    /**
+     * Name of the metric set.
+     *
+     * @return Name of the metrics set.
+     */
+    public String name() {
+        return name;
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricSetBuilder.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricSetBuilder.java
new file mode 100644
index 0000000..ff3af2b
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricSetBuilder.java
@@ -0,0 +1,219 @@
+/*
+ * 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.metrics;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.DoubleSupplier;
+import java.util.function.IntSupplier;
+import java.util.function.LongSupplier;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Metric set builder.
+ */
+public class MetricSetBuilder {
+    /** Metrics set name. */
+    private final String name;
+
+    /** Registered metrics. */
+    private Map<String, Metric> metrics = new LinkedHashMap<>();
+
+    /**
+     *  Creates a new instance of metrics set builder with given name.
+     *
+     * @param name Name of metrics set. Can't be null.
+     */
+    public MetricSetBuilder(String name) {
+        Objects.requireNonNull(name, "Metrics set name can't be null");
+        this.name = name;
+    }
+
+    /**
+     * Build a metric set.
+     *
+     * @return Metric set.
+     */
+    public MetricSet build() {
+        if (metrics == null) {
+            throw new IllegalStateException("Builder can't be used twice.");
+        }
+
+        MetricSet reg = new MetricSet(name, metrics);
+
+        metrics = null;
+
+        return reg;
+    }
+
+    /**
+     * Returns metrics set name.
+     *
+     * @return Metrics set name.
+     */
+    public String name() {
+        return name;
+    }
+
+    /**
+     * Adds existing metric with the specified name.
+     *
+     * @param metric Metric.
+     * @throws IllegalStateException If metric with given name is already added.
+     */
+    @SuppressWarnings("unchecked")
+    public  <T extends Metric> T register(T metric) {
+        T old = (T) metrics.putIfAbsent(metric.name(), metric);
+
+        if (old != null) {
+            throw new IllegalStateException("Metric with given name is already registered [name=" + name
+                    + ", metric=" + metric + ']');
+        }
+
+        return metric;
+    }
+
+    /**
+     * Add an atomic integer metric.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @return Atomic integer metric.
+     */
+    public AtomicIntMetric atomicInt(String name, @Nullable String description) {
+        return register(new AtomicIntMetric(name, description));
+    }
+
+    /**
+     * Add an integer gauge.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @param supplier Supplier of the value.
+     * @return Integer gauge.
+     */
+    public IntGauge intGauge(String name, @Nullable String description, IntSupplier supplier) {
+        return register(new IntGauge(name, description, supplier));
+    }
+
+    /**
+     * Add an atomic long metric.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @return Atomic long metric.
+     */
+    public AtomicLongMetric atomicLong(String name, @Nullable String description) {
+        return register(new AtomicLongMetric(name, description));
+    }
+
+    /**
+     * Add a long adder metric.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @return Long adder metric.
+     */
+    public LongAdderMetric longAdder(String name, @Nullable String description) {
+        return register(new LongAdderMetric(name, description));
+    }
+
+    /**
+     * Add a long gauge.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @param supplier Supplier of the value.
+     * @return Long gauge.
+     */
+    public LongGauge longGauge(String name, @Nullable String description, LongSupplier supplier) {
+        return register(new LongGauge(name, description, supplier));
+    }
+
+    /**
+     * Add an atomic double metric.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @return Atomic double metric.
+     */
+    public AtomicDoubleMetric atomicDouble(String name, @Nullable String description) {
+        return register(new AtomicDoubleMetric(name, description));
+    }
+
+    /**
+     * Add a double adder metric.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @return Double adder metric.
+     */
+    public DoubleAdderMetric doubleAdder(String name, @Nullable String description) {
+        return register(new DoubleAdderMetric(name, description));
+    }
+
+    /**
+     * Add a double gauge.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @param supplier Supplier of the value.
+     * @return Double gauge.
+     */
+    public DoubleGauge doubleGauge(String name, @Nullable String description, DoubleSupplier supplier) {
+        return register(new DoubleGauge(name, description, supplier));
+    }
+
+    /**
+     * Add a hit rate metric.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @param rateTimeInterval Rate time interval in milliseconds.
+     * @return Hit rate metric.
+     */
+    public HitRateMetric hitRate(String name, @Nullable String description, long rateTimeInterval) {
+        return register(new HitRateMetric(name, description, rateTimeInterval));
+    }
+
+    /**
+     * Add a hit rate metric.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @param rateTimeInterval Rate time interval in milliseconds.
+     * @param size Counters array size.
+     * @return Hit rate metric.
+     */
+    public HitRateMetric hitRate(String name, @Nullable String description, long rateTimeInterval, int size) {
+        return register(new HitRateMetric(name, description, rateTimeInterval, size));
+    }
+
+    /**
+     * Add a distribution metric.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @param bounds Bounds of the buckets
+     * @return Distribution metrics.
+     */
+    public DistributionMetric distribution(String name, @Nullable String description, long[] bounds) {
+        return register(new DistributionMetric(name, description, bounds));
+    }
+}
diff --git a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricSource.java b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricSource.java
new file mode 100644
index 0000000..4c4c1c2
--- /dev/null
+++ b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/MetricSource.java
@@ -0,0 +1,56 @@
+/*
+ * 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.metrics;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Interface for all metrics sources. Metric source provides access to related metrics. Metrics source always corresponds
+ * to some component or entity of the system (e.g. node is a metrics source, storage engine is a metric source). Metrics source
+ * has a unique name and type (e.g. class name). The metrics source exposes an interface for modification of metrics values.
+ * Instead of looking up metrics by a name and modifying metrics value directly, developers must use methods defined in the
+ * metrics source.
+ */
+public interface MetricSource {
+    /**
+     * Returns metric source name.
+     *
+     * @return Metric source name.
+     */
+    String name();
+
+    /**
+     * Enables metrics for metric source. Creates and returns {@link MetricSet} built during enabling. Nothing happens if
+     * the metrics are already enabled for this source.
+     *
+     * @return Newly created {@link MetricSet} instance or {@code null} if metrics are already enabled.
+     */
+    @Nullable MetricSet enable();
+
+    /**
+     * Disables metrics for metric source. Nothing happens if the metrics are already disabled for this source.
+     */
+    void disable();
+
+    /**
+     * Checks whether metrics is enabled (switched on) or not (switched off) for metric source.
+     *
+     * @return {@code True} if metrics are enabled, otherwise - {@code false}.
+     */
+    boolean enabled();
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractDoubleMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractDoubleMetricTest.java
new file mode 100644
index 0000000..7cab455
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractDoubleMetricTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metrics;
+
+/**
+ * Abstract test for double metrics.
+ */
+public abstract class AbstractDoubleMetricTest extends AbstractScalarMetricsTest<DoubleMetric, Double> {
+    /** Test value. */
+    private static final double TEST_VALUE = 100.0;
+
+    /** Expected value. */
+    private double expected = 0;
+
+    /** {@inheritDoc} */
+    @Override protected Double value(DoubleMetric metric) {
+        return metric.value();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected Double expected() {
+        return expected;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void increment(DoubleMetric metric) {
+        expected++;
+        increment0(metric);
+    }
+
+    /**
+     * Increment the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void increment0(DoubleMetric metric);
+
+    /** {@inheritDoc} */
+    @Override protected void decrement(DoubleMetric metric) {
+        expected--;
+        decrement0(metric);
+    }
+
+    /**
+     * Decrement the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void decrement0(DoubleMetric metric);
+
+    /** {@inheritDoc} */
+    @Override protected void add(DoubleMetric metric) {
+        expected += TEST_VALUE;
+        add0(metric, TEST_VALUE);
+    }
+
+    /**
+     * Add the value to the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void add0(DoubleMetric metric, double value);
+
+    /** {@inheritDoc} */
+    @Override protected void setValue(DoubleMetric metric) {
+        expected = TEST_VALUE;
+        setValue0(metric, TEST_VALUE);
+    }
+
+    /**
+     * Assign the value to the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void setValue0(DoubleMetric metric, double value);
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractIntMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractIntMetricTest.java
new file mode 100644
index 0000000..74b16e1
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractIntMetricTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metrics;
+
+/**
+ * Abstract test for integer metrics.
+ */
+public abstract class AbstractIntMetricTest extends AbstractScalarMetricsTest<IntMetric, Integer> {
+    /** Test value. */
+    private static final int TEST_VALUE = 100;
+
+    /** Expected value. */
+    private int expected = 0;
+
+    /** {@inheritDoc} */
+    @Override protected Integer value(IntMetric metric) {
+        return metric.value();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected Integer expected() {
+        return expected;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void increment(IntMetric metric) {
+        expected++;
+        increment0(metric);
+    }
+
+    /**
+     * Increment the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void increment0(IntMetric metric);
+
+    /** {@inheritDoc} */
+    @Override protected void decrement(IntMetric metric) {
+        expected--;
+        decrement0(metric);
+    }
+
+    /**
+     * Decrement the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void decrement0(IntMetric metric);
+
+    /** {@inheritDoc} */
+    @Override protected void add(IntMetric metric) {
+        expected += TEST_VALUE;
+        add0(metric, TEST_VALUE);
+    }
+
+    /**
+     * Add the value to the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void add0(IntMetric metric, int value);
+
+    /** {@inheritDoc} */
+    @Override protected void setValue(IntMetric metric) {
+        expected = TEST_VALUE;
+        setValue0(metric, TEST_VALUE);
+    }
+
+    /**
+     * Assign the value to the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void setValue0(IntMetric metric, int value);
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractLongMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractLongMetricTest.java
new file mode 100644
index 0000000..4305d86
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractLongMetricTest.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metrics;
+
+/**
+ * Abstract test for long metrics.
+ */
+public abstract class AbstractLongMetricTest extends AbstractScalarMetricsTest<LongMetric, Long> {
+    /** Test value. */
+    private static final long TEST_VALUE = 100;
+
+    /** Expected value. */
+    private long expected = 0;
+
+    /** {@inheritDoc} */
+    @Override protected Long value(LongMetric metric) {
+        return metric.value();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected Long expected() {
+        return expected;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void increment(LongMetric metric) {
+        expected++;
+        increment0(metric);
+    }
+
+    /**
+     * Increment the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void increment0(LongMetric metric);
+
+    /** {@inheritDoc} */
+    @Override protected void decrement(LongMetric metric) {
+        expected--;
+        decrement0(metric);
+    }
+
+    /**
+     * Decrement the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void decrement0(LongMetric metric);
+
+    /** {@inheritDoc} */
+    @Override protected void add(LongMetric metric) {
+        expected += TEST_VALUE;
+        add0(metric, TEST_VALUE);
+    }
+
+    /**
+     * Add the value to the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void add0(LongMetric metric, long value);
+
+    /** {@inheritDoc} */
+    @Override protected void setValue(LongMetric metric) {
+        expected = TEST_VALUE;
+        setValue0(metric, TEST_VALUE);
+    }
+
+    /**
+     * Assign the value to the metric.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void setValue0(LongMetric metric, long value);
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractScalarMetricsTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractScalarMetricsTest.java
new file mode 100644
index 0000000..42b62ce
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AbstractScalarMetricsTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.metrics;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Abstract test for scalar metrics.
+ *
+ * @param <M> Metric type.
+ * @param <V> Type of metric value.
+ */
+public abstract class AbstractScalarMetricsTest<M extends Metric, V> {
+    @Test
+    public void testCreateMetric() {
+        String name = "testName";
+        String description = "testDescription";
+
+        M m = createMetric(name, description);
+
+        assertEquals(name, m.name());
+        assertEquals(description, m.description());
+
+        assertEquals(expected(), value(m));
+        assertEquals(expected().toString(), value(m).toString());
+    }
+
+    @Test
+    public void testIncrement() {
+        M m = createMetric();
+
+        increment(m);
+        assertEquals(expected(), value(m));
+        assertEquals(expected().toString(), value(m).toString());
+    }
+
+    @Test
+    public void testDecrement() {
+        M m = createMetric();
+
+        decrement(m);
+        assertEquals(expected(), value(m));
+        assertEquals(expected().toString(), value(m).toString());
+    }
+
+    @Test
+    public void testAdd() {
+        M m = createMetric();
+
+        add(m);
+        assertEquals(expected(), value(m));
+        assertEquals(expected().toString(), value(m).toString());
+    }
+
+    @Test
+    public void testSetValue() {
+        M m = createMetric();
+
+        setValue(m);
+        assertEquals(expected(), value(m));
+        assertEquals(expected().toString(), value(m).toString());
+    }
+
+    /**
+     * Create a metric.
+     *
+     * @return Metric.
+     */
+    private M createMetric() {
+        String name = "testName";
+        String description = "testDescription";
+
+        return createMetric(name, description);
+    }
+
+    /**
+     * Create a metric with given name and description.
+     *
+     * @param name Name.
+     * @param description Description.
+     * @return Metric.
+     */
+    protected abstract M createMetric(String name, String description);
+
+    /**
+     * Return a value of the metric.
+     *
+     * @param metric Metric.
+     * @return Value of the metric.
+     */
+    protected abstract V value(M metric);
+
+    /**
+     * Expected value of the metric in current moment of time.
+     *
+     * @return Expected value.
+     */
+    protected abstract V expected();
+
+    /**
+     * Increments the metric, if applicable.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void increment(M metric);
+
+    /**
+     * Decrements the metric, if applicable.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void decrement(M metric);
+
+    /**
+     * Add some test value to the metric, if applicable.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void add(M metric);
+
+    /**
+     * Assign some test value to the metric, if applicable.
+     *
+     * @param metric Metric.
+     */
+    protected abstract void setValue(M metric);
+}
+
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AtomicDoubleMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AtomicDoubleMetricTest.java
new file mode 100644
index 0000000..5e4df04
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AtomicDoubleMetricTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.metrics;
+
+/**
+ * Test for {@link AtomicDoubleMetric}.
+ */
+public class AtomicDoubleMetricTest extends AbstractDoubleMetricTest {
+    /** {@inheritDoc} */
+    @Override protected void increment0(DoubleMetric metric) {
+        ((AtomicDoubleMetric) metric).add(1);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void decrement0(DoubleMetric metric) {
+        ((AtomicDoubleMetric) metric).add(-1);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void add0(DoubleMetric metric, double value) {
+        ((AtomicDoubleMetric) metric).add(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void setValue0(DoubleMetric metric, double value) {
+        ((AtomicDoubleMetric) metric).value(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected DoubleMetric createMetric(String name, String description) {
+        return new AtomicDoubleMetric(name, description);
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AtomicIntMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AtomicIntMetricTest.java
new file mode 100644
index 0000000..e87f0aa
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AtomicIntMetricTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.metrics;
+
+/**
+ * Test for {@link AtomicIntMetric}.
+ */
+public class AtomicIntMetricTest extends AbstractIntMetricTest {
+    /** {@inheritDoc} */
+    @Override protected AtomicIntMetric createMetric(String name, String description) {
+        return new AtomicIntMetric(name, description);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void increment0(IntMetric metric) {
+        ((AtomicIntMetric) metric).increment();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void decrement0(IntMetric metric) {
+        ((AtomicIntMetric) metric).decrement();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void add0(IntMetric metric, int value) {
+        ((AtomicIntMetric) metric).add(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void setValue0(IntMetric metric, int value) {
+        ((AtomicIntMetric) metric).value(value);
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AtomicLongMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AtomicLongMetricTest.java
new file mode 100644
index 0000000..9d7e672
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/AtomicLongMetricTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.metrics;
+
+/**
+ * Test for {@link AtomicLongMetric}.
+ */
+public class AtomicLongMetricTest extends AbstractLongMetricTest {
+    /** {@inheritDoc} */
+    @Override protected void increment0(LongMetric metric) {
+        ((AtomicLongMetric) metric).increment();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void decrement0(LongMetric metric) {
+        ((AtomicLongMetric) metric).decrement();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void add0(LongMetric metric, long value) {
+        ((AtomicLongMetric) metric).add(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void setValue0(LongMetric metric, long value) {
+        ((AtomicLongMetric) metric).value(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected LongMetric createMetric(String name, String description) {
+        return new AtomicLongMetric(name, description);
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/DistributionMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/DistributionMetricTest.java
new file mode 100644
index 0000000..b0d1484
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/DistributionMetricTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.metrics;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Test for {@link DistributionMetric}.
+ */
+public class DistributionMetricTest {
+    /**
+     * Check that calling constructor with invalid parameters causes errors.
+     */
+    @Test
+    public void creationTest() {
+        assertThrows(Throwable.class, () -> new DistributionMetric("test", null, null));
+        assertThrows(AssertionError.class, () -> new DistributionMetric("test", null, new long[0]));
+        assertThrows(AssertionError.class, () -> new DistributionMetric("test", null, new long[] { 10, 1}));
+        assertThrows(AssertionError.class, () -> new DistributionMetric("test", null, new long[] { 1, 10, 10, 100}));
+        assertThrows(AssertionError.class, () -> new DistributionMetric("test", null, new long[] { -1, 1, 10, 100}));
+    }
+
+    /**
+     * Create a distribution metric and fill it with some values.
+     *
+     * @return Metric.
+     */
+    private DistributionMetric createAndPrepareMetric() {
+        long[] bounds = new long[] {50, 500};
+
+        DistributionMetric distribution = new DistributionMetric("distribution", null, bounds);
+
+        assertEquals(bounds.length + 1, distribution.value().length);
+
+        distribution.add(10);
+        distribution.add(51);
+        distribution.add(60);
+        distribution.add(600);
+        distribution.add(600);
+        distribution.add(600);
+
+        return distribution;
+    }
+
+    /**
+     * Check that the values in distribution buckets are correct.
+     */
+    @Test
+    public void testBucketValues() {
+        DistributionMetric distribution = createAndPrepareMetric();
+
+        distribution.value();
+
+        assertEquals(1, distribution.value()[0]);
+        assertEquals(2, distribution.value()[1]);
+        assertEquals(3, distribution.value()[2]);
+    }
+
+    /**
+     * Test scalar metrics that are returned by {@link DistributionMetric#asScalarMetrics()}.
+     */
+    @Test
+    public void testScalarMetrics() {
+        DistributionMetric distribution = createAndPrepareMetric();
+
+        List<Metric> scalarMetrics = distribution.asScalarMetrics();
+
+        assertEquals("distribution_0_50", scalarMetrics.get(0).name());
+        assertEquals("distribution_50_500", scalarMetrics.get(1).name());
+        assertEquals("distribution_500_inf", scalarMetrics.get(2).name());
+
+        for (int i = 0; i < scalarMetrics.size(); i++) {
+            LongMetric lm = (LongMetric) scalarMetrics.get(i);
+            assertEquals(i + 1, lm.value());
+        }
+
+        distribution.add(1);
+        distribution.add(100);
+        distribution.add(1000);
+
+        for (int i = 0; i < scalarMetrics.size(); i++) {
+            LongMetric lm = (LongMetric) scalarMetrics.get(i);
+            assertEquals(i + 2, lm.value());
+        }
+    }
+
+    /**
+     * Test the correctness of {@link DistributionMetric#getValueAsString()}.
+     */
+    @Test
+    public void testGetValueAsString() {
+        DistributionMetric distribution = createAndPrepareMetric();
+
+        assertEquals("[distribution_0_50:1, distribution_50_500:2, distribution_500_inf:3]", distribution.getValueAsString());
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/DoubleAdderMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/DoubleAdderMetricTest.java
new file mode 100644
index 0000000..4725ad2
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/DoubleAdderMetricTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.metrics;
+
+/**
+ * Test for {@link DoubleAdderMetric}.
+ */
+public class DoubleAdderMetricTest extends AbstractDoubleMetricTest {
+    /** {@inheritDoc} */
+    @Override protected void increment0(DoubleMetric metric) {
+        ((DoubleAdderMetric) metric).add(1);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void decrement0(DoubleMetric metric) {
+        ((DoubleAdderMetric) metric).add(-1);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void add0(DoubleMetric metric, double value) {
+        ((DoubleAdderMetric) metric).add(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void setValue0(DoubleMetric metric, double value) {
+        ((DoubleAdderMetric) metric).value(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected DoubleMetric createMetric(String name, String description) {
+        return new DoubleAdderMetric(name, description);
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/DoubleGaugeTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/DoubleGaugeTest.java
new file mode 100644
index 0000000..a5f177d
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/DoubleGaugeTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.metrics;
+
+/**
+ * Test for {@link DoubleGauge}.
+ */
+public class DoubleGaugeTest extends AbstractDoubleMetricTest {
+    /** A value for the supplier. */
+    private double value = 0;
+
+    /** {@inheritDoc} */
+    @Override protected void increment0(DoubleMetric metric) {
+        value++;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void decrement0(DoubleMetric metric) {
+        value--;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void add0(DoubleMetric metric, double value) {
+        this.value += value;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void setValue0(DoubleMetric metric, double value) {
+        this.value = value;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected DoubleMetric createMetric(String name, String description) {
+        return new DoubleGauge(name, description, () -> value);
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/HitRateMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/HitRateMetricTest.java
new file mode 100644
index 0000000..a7c5353
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/HitRateMetricTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.metrics;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Hit rate metric test.
+ */
+public class HitRateMetricTest {
+    @Test
+    public void testHitRateMetric() {
+        HitRateMetric hitRateMetric = new HitRateMetric("hitRate", null, 100);
+
+        hitRateMetric.increment();
+
+        doSleep(5);
+        hitRateMetric.add(2);
+
+        doSleep(20);
+
+        hitRateMetric.increment();
+
+        assertEquals(4, hitRateMetric.value());
+
+        doSleep(100);
+        hitRateMetric.increment();
+
+        assertEquals(1, hitRateMetric.value());
+    }
+
+    private void doSleep(long time) {
+        try {
+            Thread.sleep(time);
+        } catch (InterruptedException e) {
+            // No-op.
+        }
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/IntGaugeTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/IntGaugeTest.java
new file mode 100644
index 0000000..e341dfa
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/IntGaugeTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.metrics;
+
+/**
+ * Test for {@link IntGauge}.
+ */
+public class IntGaugeTest extends AbstractIntMetricTest {
+    /** A value for the supplier. */
+    private int value;
+
+    /** {@inheritDoc} */
+    @Override protected void increment0(IntMetric metric) {
+        value++;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void decrement0(IntMetric metric) {
+        value--;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void add0(IntMetric metric, int value) {
+        this.value += value;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void setValue0(IntMetric metric, int value) {
+        this.value = value;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected IntMetric createMetric(String name, String description) {
+        return new IntGauge(name, description, () -> value);
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/LongAdderMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/LongAdderMetricTest.java
new file mode 100644
index 0000000..641d2f9
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/LongAdderMetricTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.metrics;
+
+/**
+ * Test for {@link LongAdderMetric}.
+ */
+public class LongAdderMetricTest extends AbstractLongMetricTest {
+    /** {@inheritDoc} */
+    @Override protected void increment0(LongMetric metric) {
+        ((LongAdderMetric) metric).increment();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void decrement0(LongMetric metric) {
+        ((LongAdderMetric) metric).decrement();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void add0(LongMetric metric, long value) {
+        ((LongAdderMetric) metric).add(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void setValue0(LongMetric metric, long value) {
+        ((LongAdderMetric) metric).value(value);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected LongMetric createMetric(String name, String description) {
+        return new LongAdderMetric(name, description);
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/LongGaugeMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/LongGaugeMetricTest.java
new file mode 100644
index 0000000..0dd25ca
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/LongGaugeMetricTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.metrics;
+
+/**
+ * Test for {@link LongGauge}.
+ */
+public class LongGaugeMetricTest extends AbstractLongMetricTest {
+    /** A value for the supplier. */
+    private long value = 0;
+
+    /** {@inheritDoc} */
+    @Override protected void increment0(LongMetric metric) {
+        value++;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void decrement0(LongMetric metric) {
+        value--;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void add0(LongMetric metric, long value) {
+        this.value += value;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void setValue0(LongMetric metric, long value) {
+        this.value = value;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected LongMetric createMetric(String name, String description) {
+        return new LongGauge(name, description, () -> value);
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/MetricEntitiesTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/MetricEntitiesTest.java
new file mode 100644
index 0000000..6b05321
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/MetricEntitiesTest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.metrics;
+
+import static java.util.Spliterators.spliteratorUnknownSize;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.StreamSupport.stream;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for metric entities, such as {@link MetricRegistry} and {@link MetricSet}.
+ */
+public class MetricEntitiesTest {
+    private static final String SOURCE_NAME = "testSource";
+    private static final String SCALAR_METRIC_NAME = "testScalarMetric";
+    private static final String COMPOSITE_METRIC_NAME = "testCompositeMetric";
+    private static final long[] DISTRIBUTION_BOUNDS = new long[] { 10, 100, 1000 };
+
+    @Test
+    public void testMetricLifecycle() {
+        MetricRegistry registry = new MetricRegistry();
+
+        MetricSource metricSource = new TestMetricSource();
+
+        registry.registerSource(metricSource);
+        assertEquals(0L, registry.metricSnapshot().get2());
+
+        assertThrows(IllegalStateException.class, () -> registry.registerSource(metricSource));
+
+        assertEquals(0L, registry.metricSnapshot().get2());
+        assertTrue(registry.metricSnapshot().get1().isEmpty());
+
+        MetricSource alreadyEnabled = new TestMetricSource("alreadyEnabled");
+        alreadyEnabled.enable();
+        assertThrows(AssertionError.class, () -> registry.registerSource(alreadyEnabled));
+        assertEquals(0L, registry.metricSnapshot().get2());
+
+        // Enabling metric source, metric snapshot and its version should be changed.
+        MetricSet metricSet = registry.enable(SOURCE_NAME);
+        assertNotNull(metricSet);
+        assertEquals(1L, registry.metricSnapshot().get2());
+        assertFalse(registry.metricSnapshot().get1().isEmpty());
+        assertNull(registry.enable(metricSource));
+
+        assertThrows(IllegalStateException.class, () -> registry.enable("unexisting"));
+        assertEquals(1L, registry.metricSnapshot().get2());
+
+        // Enabling the metric source that was already enabled before, metric snapshot should not be changed.
+        assertNull(registry.enable(SOURCE_NAME));
+        IgniteBiTuple<Map<String, MetricSet>, Long> metricSnapshot = registry.metricSnapshot();
+        assertEquals(1L, metricSnapshot.get2());
+        assertFalse(metricSnapshot.get1().isEmpty());
+        MetricSet ms = metricSnapshot.get1().get(SOURCE_NAME);
+        assertEquals(metricSet, ms);
+
+        // Disable the metric source.
+        registry.disable(SOURCE_NAME);
+        assertEquals(2L, registry.metricSnapshot().get2());
+
+        // Disable unexisting metric source, exception is thrown, metric snapshot should not be changed.
+        assertThrows(IllegalStateException.class, () -> registry.disable("unexisting"));
+        metricSnapshot = registry.metricSnapshot();
+        assertEquals(2L, metricSnapshot.get2());
+        assertTrue(metricSnapshot.get1().isEmpty());
+
+        // Trying to disable the metric source that was already disabled before, metric snapshot should not be changed.
+        registry.disable(SOURCE_NAME);
+        assertEquals(2L, registry.metricSnapshot().get2());
+        registry.disable(metricSource);
+        assertEquals(2L, registry.metricSnapshot().get2());
+
+        // Enabling metric source again, metric snapshot changes.
+        registry.enable(metricSource);
+        assertEquals(3L, registry.metricSnapshot().get2());
+        assertFalse(registry.metricSnapshot().get1().isEmpty());
+
+        // Unregister enabled metric source, it should be disabled, metric snapshot should be changed.
+        registry.unregisterSource(metricSource);
+        assertEquals(4L, registry.metricSnapshot().get2());
+        assertTrue(registry.metricSnapshot().get1().isEmpty());
+
+        // Trying to unregister the metric source that was already unregistered before, metric snapshot should not be changed.
+        assertThrows(IllegalStateException.class, () -> registry.unregisterSource(metricSource));
+        metricSnapshot = registry.metricSnapshot();
+        assertEquals(4L, metricSnapshot.get2());
+        assertTrue(metricSnapshot.get1().isEmpty());
+    }
+
+    @Test
+    public void testMetricSet() {
+        MetricRegistry registry = new MetricRegistry();
+
+        TestMetricSource metricSource = new TestMetricSource();
+
+        registry.registerSource(metricSource);
+
+        assertNull(metricSource.holder());
+
+        MetricSet metricSet = registry.enable(metricSource.name());
+
+        TestMetricSource.Holder holder = metricSource.holder();
+
+        assertNotNull(holder);
+
+        assertTrue(metricSet.get(SCALAR_METRIC_NAME) instanceof IntMetric);
+        assertTrue(metricSet.get(COMPOSITE_METRIC_NAME) instanceof DistributionMetric);
+
+        List<Metric> metrics = stream(spliteratorUnknownSize(metricSet.iterator(), 0), false).collect(toList());
+        assertEquals(2, metrics.size());
+
+        assertEquals(SCALAR_METRIC_NAME, holder.atomicIntMetric.name());
+        assertEquals(COMPOSITE_METRIC_NAME, holder.distributionMetric.name());
+
+        List<Metric> scalarMetrics = stream(spliteratorUnknownSize(new CompositeAwareIterator(metrics.iterator()), 0), false)
+                .collect(toList());
+
+        assertEquals(2 + DISTRIBUTION_BOUNDS.length, scalarMetrics.size());
+
+        assertEquals(SCALAR_METRIC_NAME, scalarMetrics.get(0).name());
+        assertEquals(COMPOSITE_METRIC_NAME + "_0_" + DISTRIBUTION_BOUNDS[0], scalarMetrics.get(1).name());
+        assertEquals(COMPOSITE_METRIC_NAME + '_' + DISTRIBUTION_BOUNDS[0] + "_" + DISTRIBUTION_BOUNDS[1], scalarMetrics.get(2).name());
+        assertEquals(COMPOSITE_METRIC_NAME + '_' + DISTRIBUTION_BOUNDS[1] + "_" + DISTRIBUTION_BOUNDS[2], scalarMetrics.get(3).name());
+        assertEquals(COMPOSITE_METRIC_NAME + '_' + DISTRIBUTION_BOUNDS[2] + "_inf", scalarMetrics.get(4).name());
+
+        registry.disable(metricSource.name());
+
+        assertNull(metricSource.holder());
+    }
+
+    private static class TestMetricSource extends AbstractMetricSource<TestMetricSource.Holder> {
+        protected TestMetricSource() {
+            super(SOURCE_NAME);
+        }
+
+
+        protected TestMetricSource(String name) {
+            super(name);
+        }
+
+        @Override protected Holder createHolder() {
+            return new Holder();
+        }
+
+        @Override protected void init(MetricSetBuilder bldr, Holder holder) {
+            bldr.register(holder.atomicIntMetric);
+            bldr.register(holder.distributionMetric);
+        }
+
+        private static class Holder implements AbstractMetricSource.Holder<Holder> {
+            final AtomicIntMetric atomicIntMetric = new AtomicIntMetric(SCALAR_METRIC_NAME, null);
+            final DistributionMetric distributionMetric = new DistributionMetric(COMPOSITE_METRIC_NAME, null, DISTRIBUTION_BOUNDS);
+        }
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/ThreadPoolMetricSource.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/ThreadPoolMetricSource.java
new file mode 100644
index 0000000..ca4f116
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/ThreadPoolMetricSource.java
@@ -0,0 +1,99 @@
+/*
+ * 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.metrics;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ * Metric source for {@link ThreadPoolMetricTest}.
+ */
+public class ThreadPoolMetricSource extends AbstractMetricSource<ThreadPoolMetricSource.Holder> {
+    private final ThreadPoolExecutor exec;
+
+    /**
+     * Constructor.
+     *
+     * @param name Name.
+     * @param exec Executor.
+     */
+    public ThreadPoolMetricSource(String name, ThreadPoolExecutor exec) {
+        super(name);
+
+        this.exec = exec;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected Holder createHolder() {
+        return new Holder();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void init(MetricSetBuilder bldr, Holder holder) {
+        bldr.intGauge(
+                "ActiveCount",
+                "Approximate number of threads that are actively executing tasks.",
+                exec::getActiveCount
+        );
+
+        bldr.longGauge(
+                "CompletedTaskCount",
+                "Approximate total number of tasks that have completed execution.",
+                exec::getCompletedTaskCount
+        );
+
+        bldr.intGauge("CorePoolSize", "The core number of threads.", exec::getCorePoolSize);
+
+        bldr.intGauge(
+                "LargestPoolSize",
+                "Largest number of threads that have ever simultaneously been in the pool.",
+                exec::getLargestPoolSize
+        );
+
+        bldr.intGauge(
+                "MaximumPoolSize",
+                "The maximum allowed number of threads.",
+                exec::getMaximumPoolSize
+        );
+
+        bldr.intGauge("PoolSize", "Current number of threads in the pool.", exec::getPoolSize);
+
+        bldr.longGauge(
+                "TaskCount",
+                "Approximate total number of tasks that have been scheduled for execution.",
+                exec::getTaskCount
+        );
+
+        bldr.intGauge("QueueSize", "Current size of the execution queue.", () -> exec.getQueue().size());
+
+        bldr.longGauge(
+                "KeepAliveTime",
+                "Thread keep-alive time, which is the amount of time which threads in excess of "
+                        + "the core pool size may remain idle before being terminated.",
+                () -> exec.getKeepAliveTime(MILLISECONDS)
+        );
+    }
+
+    /**
+     * Holder class.
+     */
+    protected static class Holder implements AbstractMetricSource.Holder<Holder> {
+        // No-op.
+    }
+}
diff --git a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/ThreadPoolMetricTest.java b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/ThreadPoolMetricTest.java
new file mode 100644
index 0000000..c9fb0b7
--- /dev/null
+++ b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/ThreadPoolMetricTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.metrics;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Metrics for thread pool.
+ */
+public class ThreadPoolMetricTest {
+    @Test
+    public void test() throws ExecutionException, InterruptedException {
+        // Should be one per node.
+        MetricRegistry registry = new MetricRegistry();
+
+        // ------------------------------------------------------------------------
+
+        // System component, e.g. thread pool executor
+        ThreadPoolExecutor exec = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
+
+        // Metrics source for thread pool
+        ThreadPoolMetricSource src = new ThreadPoolMetricSource("example.thread_pool.ExamplePool", exec);
+
+        // Register source after the component created.
+        registry.registerSource(src);
+
+        // ------------------------------------------------------------------------
+
+        // Enable metrics by signal (or because configuration)
+        MetricSet metricSet = registry.enable(src.name());
+
+        LongMetric completedTaskCount = metricSet.get("CompletedTaskCount");
+
+        assertEquals(0L, completedTaskCount.value());
+
+        exec.submit(() -> {}).get();
+        assertEquals(1L, completedTaskCount.value());
+
+        // ------------------------------------------------------------------------
+
+        // Disable metrics by signal
+        registry.disable(src.name());
+
+        // Component is stopped\destroyed
+        registry.unregisterSource(src);
+        exec.shutdown();
+    }
+}
diff --git a/modules/runner/pom.xml b/modules/runner/pom.xml
index 5cbc1e7..ae0605d 100644
--- a/modules/runner/pom.xml
+++ b/modules/runner/pom.xml
@@ -118,6 +118,11 @@
             <artifactId>ignite-storage-page-memory</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.apache.ignite</groupId>
+            <artifactId>ignite-metrics</artifactId>
+        </dependency>
+
         <!-- 3rd party dependencies -->
         <dependency>
             <groupId>org.slf4j</groupId>
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index 400b345..cf8a1b6 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -59,6 +59,7 @@
 import org.apache.ignite.internal.logger.Loggers;
 import org.apache.ignite.internal.metastorage.MetaStorageManager;
 import org.apache.ignite.internal.metastorage.server.persistence.RocksDbKeyValueStorage;
+import org.apache.ignite.internal.metrics.MetricManager;
 import org.apache.ignite.internal.raft.Loza;
 import org.apache.ignite.internal.recovery.ConfigurationCatchUpListener;
 import org.apache.ignite.internal.recovery.RecoveryCompletionFutureFactory;
@@ -198,6 +199,9 @@
     /** Schema manager. */
     private final SchemaManager schemaManager;
 
+    /** Metric manager. */
+    private final MetricManager metricManager;
+
     /**
      * The Constructor.
      *
@@ -215,6 +219,8 @@
 
         vaultMgr = createVault(workDir);
 
+        metricManager = new MetricManager();
+
         ConfigurationModules modules = loadConfigurationModules(serviceProviderClassLoader);
 
         nodeCfgMgr = new ConfigurationManager(
@@ -428,6 +434,7 @@
 
             // Start the components that are required to join the cluster.
             lifecycleManager.startComponents(
+                    metricManager,
                     nettyBootstrapFactory,
                     clusterSvc,
                     restComponent,
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/AsyncResultSetImpl.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/AsyncResultSetImpl.java
index ce2e0fc..7f673e1 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/AsyncResultSetImpl.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/api/AsyncResultSetImpl.java
@@ -33,7 +33,7 @@
 import org.apache.ignite.internal.sql.engine.AsyncCursor.BatchedResult;
 import org.apache.ignite.internal.sql.engine.AsyncSqlCursor;
 import org.apache.ignite.internal.sql.engine.SqlQueryType;
-import org.apache.ignite.internal.sql.engine.util.TransformingIterator;
+import org.apache.ignite.internal.util.TransformingIterator;
 import org.apache.ignite.sql.NoRowSetExpectedException;
 import org.apache.ignite.sql.ResultSetMetadata;
 import org.apache.ignite.sql.SqlException;
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/AbstractIndexScan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/AbstractIndexScan.java
index 028525a..2acc3f4 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/AbstractIndexScan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/AbstractIndexScan.java
@@ -22,8 +22,8 @@
 import java.util.function.Predicate;
 import java.util.function.Supplier;
 import org.apache.calcite.rel.type.RelDataType;
-import org.apache.ignite.internal.sql.engine.util.FilteringIterator;
-import org.apache.ignite.internal.sql.engine.util.TransformingIterator;
+import org.apache.ignite.internal.util.FilteringIterator;
+import org.apache.ignite.internal.util.TransformingIterator;
 import org.apache.ignite.lang.IgniteInternalException;
 
 /**
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/RuntimeHashIndex.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/RuntimeHashIndex.java
index 143f337..3d31632 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/RuntimeHashIndex.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/RuntimeHashIndex.java
@@ -28,7 +28,7 @@
 import java.util.function.Supplier;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.ignite.internal.sql.engine.exec.exp.agg.GroupKey;
-import org.apache.ignite.internal.sql.engine.util.FilteringIterator;
+import org.apache.ignite.internal.util.FilteringIterator;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/TableFunctionScan.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/TableFunctionScan.java
index 2f7822d..5e1cf4b 100644
--- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/TableFunctionScan.java
+++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/exec/TableFunctionScan.java
@@ -20,7 +20,7 @@
 import java.util.Iterator;
 import java.util.function.Supplier;
 import org.apache.ignite.internal.sql.engine.exec.RowHandler.RowFactory;
-import org.apache.ignite.internal.sql.engine.util.TransformingIterator;
+import org.apache.ignite.internal.util.TransformingIterator;
 import org.jetbrains.annotations.NotNull;
 
 /**
diff --git a/parent/pom.xml b/parent/pom.xml
index 8a7de0d..d15b3ca 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -469,6 +469,12 @@
                 <version>${project.version}</version>
             </dependency>
 
+            <dependency>
+                <groupId>org.apache.ignite</groupId>
+                <artifactId>ignite-metrics</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+
             <!-- 3rd party dependencies -->
             <dependency>
                 <groupId>org.jetbrains</groupId>
diff --git a/pom.xml b/pom.xml
index d514df6..012de13 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,6 +63,7 @@
         <module>modules/metastorage-client</module>
         <module>modules/metastorage-common</module>
         <module>modules/metastorage-server</module>
+        <module>modules/metrics</module>
         <module>modules/network</module>
         <module>modules/network-annotation-processor</module>
         <module>modules/network-api</module>