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

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


The following commit(s) were added to refs/heads/main by this push:
     new a1df12f1a6c IGNITE-28230 Implement SimpleMetricSource (#7855)
a1df12f1a6c is described below

commit a1df12f1a6c5e43af6af857100b4be4266eea751
Author: Viacheslav Blinov <[email protected]>
AuthorDate: Wed Apr 1 12:01:54 2026 +0300

    IGNITE-28230 Implement SimpleMetricSource (#7855)
---
 .../internal/metrics/AbstractMetricSource.java     |   2 +
 .../internal/metrics/SimpleMetricSource.java       | 211 ++++++++++++++++
 .../internal/metrics/SimpleMetricSourceTest.java   | 274 +++++++++++++++++++++
 3 files changed, 487 insertions(+)

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
index 2e3115cb782..4c199efc552 100644
--- 
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
@@ -26,7 +26,9 @@ import org.jetbrains.annotations.Nullable;
  * Base class for all metric sources.
  *
  * @param <T> Holder type.
+ * @deprecated Use {@link SimpleMetricSource} instead.
  */
+@Deprecated
 public abstract class AbstractMetricSource<T extends 
AbstractMetricSource.Holder<T>> implements MetricSource {
     /** Holder field updater. */
     @SuppressWarnings("rawtypes")
diff --git 
a/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/SimpleMetricSource.java
 
b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/SimpleMetricSource.java
new file mode 100644
index 00000000000..f0c26c5ce3a
--- /dev/null
+++ 
b/modules/metrics/src/main/java/org/apache/ignite/internal/metrics/SimpleMetricSource.java
@@ -0,0 +1,211 @@
+/*
+ * 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.HashMap;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import java.util.function.DoubleSupplier;
+import java.util.function.IntSupplier;
+import java.util.function.LongSupplier;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * A {@link MetricSource} with factory methods for creating and registering 
metrics.
+ *
+ * <p>Metrics are created via factory methods and stored in a {@link 
ConcurrentHashMap}.
+ * Enable/disable controls registry visibility only — metrics themselves are 
always alive
+ * and values persist across enable/disable cycles.
+ *
+ * <p>Usage:
+ * <pre>{@code
+ * public class MyMetrics {
+ *     private final LongAdderMetric requests;
+ *     private final DistributionMetric duration;
+ *
+ *     public MyMetrics(SimpleMetricSource source) {
+ *         requests = source.longAdder("Requests", "Total requests.");
+ *         duration = source.distribution("Duration", "Request duration in 
ms.",
+ *                 new long[]{1, 5, 10, 50, 100, 500});
+ *     }
+ *
+ *     public void onRequest(long durationMs) {
+ *         requests.increment();
+ *         duration.add(durationMs);
+ *     }
+ * }
+ * }</pre>
+ */
+public final class SimpleMetricSource implements MetricSource {
+    @SuppressWarnings("rawtypes")
+    private static final AtomicReferenceFieldUpdater<SimpleMetricSource, 
MetricSet> METRIC_SET_UPD =
+            AtomicReferenceFieldUpdater.newUpdater(SimpleMetricSource.class, 
MetricSet.class, "metricSet");
+
+    private final String name;
+
+    private final String description;
+
+    private final @Nullable String group;
+
+    private final ConcurrentMap<String, Metric> metrics = new 
ConcurrentHashMap<>();
+
+    /** Non-null when enabled. */
+    private volatile @Nullable MetricSet metricSet;
+
+    /**
+     * Constructor.
+     *
+     * @param name Metric source name.
+     * @param description Metric source description.
+     */
+    public SimpleMetricSource(String name, String description) {
+        this(name, description, null);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param name Metric source name.
+     * @param description Metric source description.
+     * @param group Optional group name for additional grouping in external 
systems (e.g. JMX).
+     */
+    public SimpleMetricSource(String name, String description, @Nullable 
String group) {
+        this.name = Objects.requireNonNull(name, "name");
+        this.description = Objects.requireNonNull(description, "description");
+        this.group = group;
+    }
+
+    @Override
+    public final String name() {
+        return name;
+    }
+
+    @Override
+    public String description() {
+        return description;
+    }
+
+    @Override
+    public @Nullable String group() {
+        return group;
+    }
+
+    /** Creates an {@link AtomicIntMetric}. */
+    public AtomicIntMetric atomicInt(String name, @Nullable String 
description) {
+        return register(new AtomicIntMetric(name, description));
+    }
+
+    /** Creates an {@link IntGauge}. */
+    public IntGauge intGauge(String name, @Nullable String description, 
IntSupplier supplier) {
+        return register(new IntGauge(name, description, supplier));
+    }
+
+    /** Creates an {@link AtomicLongMetric}. */
+    public AtomicLongMetric atomicLong(String name, @Nullable String 
description) {
+        return register(new AtomicLongMetric(name, description));
+    }
+
+    /** Creates a {@link LongAdderMetric}. */
+    public LongAdderMetric longAdder(String name, @Nullable String 
description) {
+        return register(new LongAdderMetric(name, description));
+    }
+
+    /** Creates a {@link LongGauge}. */
+    public LongGauge longGauge(String name, @Nullable String description, 
LongSupplier supplier) {
+        return register(new LongGauge(name, description, supplier));
+    }
+
+    /** Creates an {@link AtomicDoubleMetric}. */
+    public AtomicDoubleMetric atomicDouble(String name, @Nullable String 
description) {
+        return register(new AtomicDoubleMetric(name, description));
+    }
+
+    /** Creates a {@link DoubleAdderMetric}. */
+    public DoubleAdderMetric doubleAdder(String name, @Nullable String 
description) {
+        return register(new DoubleAdderMetric(name, description));
+    }
+
+    /** Creates a {@link DoubleGauge}. */
+    public DoubleGauge doubleGauge(String name, @Nullable String description, 
DoubleSupplier supplier) {
+        return register(new DoubleGauge(name, description, supplier));
+    }
+
+    /** Creates a {@link HitRateMetric} with default counters array size. */
+    public HitRateMetric hitRate(String name, @Nullable String description, 
long rateTimeInterval) {
+        return register(new HitRateMetric(name, description, 
rateTimeInterval));
+    }
+
+    /** Creates a {@link HitRateMetric} with custom counters array size. */
+    public HitRateMetric hitRate(String name, @Nullable String description, 
long rateTimeInterval, int size) {
+        return register(new HitRateMetric(name, description, rateTimeInterval, 
size));
+    }
+
+    /** Creates a {@link DistributionMetric}. Bounds must be sorted, unique, 
and start from {@code >= 0}. */
+    public DistributionMetric distribution(String name, @Nullable String 
description, long[] bounds) {
+        return register(new DistributionMetric(name, description, bounds));
+    }
+
+    /**
+     * Registers a pre-created metric. Use this for types without a dedicated 
factory method
+     * ({@link StringGauge}, {@link UuidGauge}, etc.).
+     *
+     * @return The same metric instance.
+     * @throws IllegalStateException If a metric with the given name is 
already registered.
+     */
+    public <T extends Metric> T register(T metric) {
+        Objects.requireNonNull(metric, "metric");
+
+        Metric existing = metrics.putIfAbsent(metric.name(), metric);
+
+        if (existing != null) {
+            throw new IllegalStateException("Metric with given name is already 
registered [sourceName="
+                    + this.name + ", metricName=" + metric.name() + ']');
+        }
+
+        return metric;
+    }
+
+    @Override
+    public @Nullable MetricSet enable() {
+        if (metricSet != null) {
+            return null;
+        }
+
+        // TODO: IGNITE-28412 consider making MetricSet observable instead of 
a single snapshot to support post-enable metric addition.
+        MetricSet newMetricSet = new MetricSet(name, description, group, new 
HashMap<>(metrics));
+
+        if (METRIC_SET_UPD.compareAndSet(this, null, newMetricSet)) {
+            return newMetricSet;
+        }
+
+        return null;
+    }
+
+    @Override
+    public void disable() {
+        METRIC_SET_UPD.set(this, null);
+    }
+
+    @Override
+    public boolean enabled() {
+        return metricSet != null;
+    }
+
+}
diff --git 
a/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/SimpleMetricSourceTest.java
 
b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/SimpleMetricSourceTest.java
new file mode 100644
index 00000000000..189e23e1809
--- /dev/null
+++ 
b/modules/metrics/src/test/java/org/apache/ignite/internal/metrics/SimpleMetricSourceTest.java
@@ -0,0 +1,274 @@
+/*
+ * 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.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests for {@link SimpleMetricSource}.
+ */
+public class SimpleMetricSourceTest {
+    private static final String SOURCE_NAME = "test.source";
+
+    private static SimpleMetricSource createSource() {
+        return new SimpleMetricSource(SOURCE_NAME, "Test source.");
+    }
+
+    private static SimpleMetricSource createEnabledSource() {
+        SimpleMetricSource source = createSource();
+        source.enable();
+        return source;
+    }
+
+    // -- Constructor and properties --
+
+    @Test
+    void properties() {
+        SimpleMetricSource source = new SimpleMetricSource("name", "desc", 
"group");
+
+        assertEquals("name", source.name());
+        assertEquals("desc", source.description());
+        assertEquals("group", source.group());
+    }
+
+    @Test
+    void groupDefaultsToNull() {
+        SimpleMetricSource source = new SimpleMetricSource("name", "desc");
+
+        assertNull(source.group());
+    }
+
+    @Test
+    void nullNameThrows() {
+        assertThrows(NullPointerException.class, () -> new 
SimpleMetricSource(null, "desc"));
+    }
+
+    @Test
+    void nullDescriptionThrows() {
+        assertThrows(NullPointerException.class, () -> new 
SimpleMetricSource("name", null));
+    }
+
+    // -- Factory methods --
+
+    @Test
+    void factoryMethodsCreateAllMetricTypes() {
+        SimpleMetricSource source = createEnabledSource();
+
+        assertNotNull(source.atomicInt("ai", null));
+        assertNotNull(source.atomicLong("al", null));
+        assertNotNull(source.longAdder("la", null));
+        assertNotNull(source.atomicDouble("ad", null));
+        assertNotNull(source.doubleAdder("da", null));
+        assertNotNull(source.intGauge("ig", null, () -> 0));
+        assertNotNull(source.longGauge("lg", null, () -> 0L));
+        assertNotNull(source.doubleGauge("dg", null, () -> 0.0));
+        assertNotNull(source.hitRate("hr", null, 1000));
+        assertNotNull(source.hitRate("hr2", null, 1000, 5));
+        assertNotNull(source.distribution("dist", null, new long[]{10, 100}));
+    }
+
+    @Test
+    void registerReturnsSameInstance() {
+        SimpleMetricSource source = createSource();
+        StringGauge gauge = new StringGauge("id", null, () -> "val");
+
+        assertSame(gauge, source.register(gauge));
+    }
+
+    @Test
+    void duplicateMetricNameThrows() {
+        SimpleMetricSource source = createSource();
+        source.atomicLong("Counter", null);
+
+        IllegalStateException ex = assertThrows(
+                IllegalStateException.class,
+                () -> source.atomicLong("Counter", null)
+        );
+
+        assertTrue(ex.getMessage().contains(SOURCE_NAME));
+        assertTrue(ex.getMessage().contains("Counter"));
+    }
+
+    @Test
+    void nullMetricThrows() {
+        assertThrows(NullPointerException.class, () -> 
createSource().register(null));
+    }
+
+    // -- Enable/disable lifecycle --
+
+    @Test
+    void notEnabledByDefault() {
+        assertFalse(createSource().enabled());
+    }
+
+    @Test
+    void enableReturnsMetricSet() {
+        SimpleMetricSource source = createSource();
+        LongAdderMetric counter = source.longAdder("Counter", null);
+
+        MetricSet metricSet = source.enable();
+
+        assertNotNull(metricSet);
+        assertTrue(source.enabled());
+        assertSame(counter, metricSet.get("Counter"));
+    }
+
+    @Test
+    void secondEnableReturnsNull() {
+        SimpleMetricSource source = createSource();
+        source.enable();
+
+        assertNull(source.enable());
+    }
+
+    @Test
+    void disableWorks() {
+        SimpleMetricSource source = createSource();
+        source.enable();
+        source.disable();
+
+        assertFalse(source.enabled());
+    }
+
+    @Test
+    void disableWhenAlreadyDisabledIsNoOp() {
+        SimpleMetricSource source = createSource();
+        source.disable();
+
+        assertFalse(source.enabled());
+    }
+
+    @Test
+    void enableEmptySource() {
+        MetricSet metricSet = createEnabledSource().enable();
+
+        // Already enabled, returns null.
+        assertNull(metricSet);
+    }
+
+    // -- Metrics record regardless of enabled state --
+
+    @Test
+    void metricsRecordWhenDisabled() {
+        SimpleMetricSource source = createSource();
+
+        LongAdderMetric counter = source.longAdder("Counter", null);
+        AtomicIntMetric intCounter = source.atomicInt("IntCounter", null);
+        DistributionMetric dist = source.distribution("Dist", null, new 
long[]{10});
+        HitRateMetric hitRate = source.hitRate("Rate", null, 60_000);
+
+        assertFalse(source.enabled());
+
+        counter.increment();
+        intCounter.increment();
+        dist.add(5);
+        hitRate.increment();
+
+        assertEquals(1L, counter.value());
+        assertEquals(1, intCounter.value());
+        assertEquals(1L, dist.value()[0]);
+    }
+
+    @Test
+    void gaugesWorkRegardlessOfEnabledState() {
+        SimpleMetricSource source = createSource();
+        int[] holder = {42};
+
+        IntGauge gauge = source.intGauge("Gauge", null, () -> holder[0]);
+
+        assertEquals(42, gauge.value());
+
+        source.enable();
+        holder[0] = 99;
+
+        assertEquals(99, gauge.value());
+    }
+
+    // -- Value persistence across cycles --
+
+    @Test
+    void valuesPreservedAcrossEnableDisableCycle() {
+        SimpleMetricSource source = createEnabledSource();
+
+        LongAdderMetric counter = source.longAdder("Counter", null);
+        counter.add(100);
+
+        source.disable();
+        MetricSet secondSet = source.enable();
+
+        assertSame(counter, secondSet.get("Counter"));
+        assertEquals(100L, counter.value());
+    }
+
+    // -- Post-enable metric addition --
+
+    @Test
+    void metricAddedAfterEnableIsFunctional() {
+        SimpleMetricSource source = createEnabledSource();
+
+        AtomicLongMetric late = source.atomicLong("Late", null);
+        late.increment();
+
+        assertTrue(source.enabled());
+        assertEquals(1L, late.value());
+    }
+
+    // -- Registry integration --
+
+    @Test
+    void worksWithMetricRegistry() {
+        MetricRegistry registry = new MetricRegistry();
+        SimpleMetricSource source = createSource();
+        LongAdderMetric counter = source.longAdder("Requests", null);
+
+        registry.registerSource(source);
+        MetricSet metricSet = registry.enable(SOURCE_NAME);
+
+        assertNotNull(metricSet);
+        assertSame(counter, metricSet.get("Requests"));
+
+        counter.increment();
+        assertEquals(1L, ((LongAdderMetric) 
metricSet.get("Requests")).value());
+    }
+
+    @Test
+    void registryDisableAndReEnable() {
+        MetricRegistry registry = new MetricRegistry();
+        SimpleMetricSource source = createSource();
+        LongAdderMetric counter = source.longAdder("Requests", null);
+
+        registry.registerSource(source);
+        registry.enable(SOURCE_NAME);
+        counter.add(10);
+
+        registry.disable(SOURCE_NAME);
+        assertFalse(source.enabled());
+
+        MetricSet reEnabled = registry.enable(source);
+        assertNotNull(reEnabled);
+        assertEquals(10L, ((LongAdderMetric) 
reEnabled.get("Requests")).value());
+    }
+}

Reply via email to