From 8b5fcbd67a1e530c98aefe5d7e8a6208dd53736f Mon Sep 17 00:00:00 2001 From: qweek Date: Fri, 22 Sep 2023 04:49:16 +0800 Subject: [PATCH 1/3] add Meter.Cache for dynamic meter registration --- .../micrometer/core/instrument/Counter.java | 9 ++ .../core/instrument/DistributionSummary.java | 9 ++ .../io/micrometer/core/instrument/Gauge.java | 14 ++ .../io/micrometer/core/instrument/Meter.java | 25 ++++ .../io/micrometer/core/instrument/Timer.java | 9 ++ .../core/instrument/MeterCacheTest.java | 122 ++++++++++++++++++ 6 files changed, 188 insertions(+) create mode 100644 micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/Counter.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/Counter.java index 4edbdd7899..6122ad3e94 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/Counter.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/Counter.java @@ -18,6 +18,7 @@ import io.micrometer.common.lang.Nullable; import java.util.Collections; +import java.util.function.Function; /** * Counters monitor monotonically increasing values. Counters may never be reset to a @@ -128,6 +129,14 @@ public Builder baseUnit(@Nullable String unit) { * @return A new or existing counter. */ public Counter register(MeterRegistry registry) { + return register(registry, tags); + } + + public Meter.Provider register(MeterRegistry registry, Function provider) { + return new Meter.Cache<>(key -> register(registry, tags.and(provider.apply(key)))); + } + + private Counter register(MeterRegistry registry, Tags tags) { return registry.counter(new Meter.Id(name, tags, baseUnit, description, Type.COUNTER)); } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/DistributionSummary.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/DistributionSummary.java index 3d497337ab..17550f0297 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/DistributionSummary.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/DistributionSummary.java @@ -24,6 +24,7 @@ import java.time.Duration; import java.util.Arrays; import java.util.concurrent.TimeUnit; +import java.util.function.Function; /** * Track the sample distribution of events. An example would be the response sizes for @@ -395,6 +396,14 @@ public Builder scale(double scale) { * @return A new or existing distribution summary. */ public DistributionSummary register(MeterRegistry registry) { + return register(registry, tags); + } + + public Meter.Provider register(MeterRegistry registry, Function provider) { + return new Meter.Cache<>(key -> register(registry, tags.and(provider.apply(key)))); + } + + private DistributionSummary register(MeterRegistry registry, Tags tags) { return registry.summary(new Meter.Id(name, tags, baseUnit, description, Type.DISTRIBUTION_SUMMARY), distributionConfigBuilder.build(), scale); } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/Gauge.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/Gauge.java index 54d2b88af8..27cd0a0be9 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/Gauge.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/Gauge.java @@ -20,6 +20,7 @@ import io.micrometer.core.instrument.distribution.HistogramGauges; import java.util.Collections; +import java.util.function.Function; import java.util.function.Supplier; import java.util.function.ToDoubleFunction; @@ -192,6 +193,19 @@ public Builder strongReference(boolean strong) { * @return A new or existing gauge. */ public Gauge register(MeterRegistry registry) { + return register(registry, obj, tags); + } + + public Meter.Provider register(MeterRegistry registry, Supplier generator, + Function provider) { + return new Meter.Cache<>(key -> { + final T obj = generator.get(); + register(registry, obj, tags.and(provider.apply(key))); + return obj; + }); + } + + private Gauge register(MeterRegistry registry, T obj, Tags tags) { return registry.gauge(new Meter.Id(name, tags, baseUnit, description, Type.GAUGE, syntheticAssociation), obj, strongReference ? new StrongReferenceGaugeFunction<>(obj, f) : f); } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/Meter.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/Meter.java index d1e5c853cf..ca87018c0e 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/Meter.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/Meter.java @@ -23,7 +23,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -483,4 +485,27 @@ public Meter register(MeterRegistry registry) { default void close() { } + interface Provider { + + V get(@Nullable K key); + + } + + class Cache implements Provider { + + private final Map cache = new ConcurrentHashMap<>(); + + private final Function mappingFunction; + + public Cache(Function mappingFunction) { + this.mappingFunction = mappingFunction; + } + + @Override + public V get(@Nullable K key) { + return cache.computeIfAbsent(key, mappingFunction); + } + + } + } diff --git a/micrometer-core/src/main/java/io/micrometer/core/instrument/Timer.java b/micrometer-core/src/main/java/io/micrometer/core/instrument/Timer.java index 8e6c90caa7..b19e8dfb54 100644 --- a/micrometer-core/src/main/java/io/micrometer/core/instrument/Timer.java +++ b/micrometer-core/src/main/java/io/micrometer/core/instrument/Timer.java @@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.BooleanSupplier; import java.util.function.DoubleSupplier; +import java.util.function.Function; import java.util.function.IntSupplier; import java.util.function.LongSupplier; import java.util.function.Supplier; @@ -439,6 +440,14 @@ public Builder description(String description) { * @return A new or existing timer. */ public Timer register(MeterRegistry registry) { + return register(registry, tags); + } + + public Meter.Provider register(MeterRegistry registry, Function provider) { + return new Meter.Cache<>(key -> register(registry, tags.and(provider.apply(key)))); + } + + private Timer register(MeterRegistry registry, Tags tags) { // the base unit for a timer will be determined by the monitoring system // implementation return registry.timer(new Meter.Id(name, tags, null, description, Type.TIMER), diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java new file mode 100644 index 0000000000..180af722f2 --- /dev/null +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java @@ -0,0 +1,122 @@ +package io.micrometer.core.instrument; + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Meter.Cache}. + * + * @author qweek + */ +public class MeterCacheTest { + + private MeterRegistry registry = new SimpleMeterRegistry(); + + @Test + void provideCounter() { + Meter.Provider counterProvider = Counter.builder("my.counter") + .register(registry, value -> value == null ? Tags.empty() : Tags.of("my.key", value)); + + assertThat(registry.getMeters()).hasSize(0); + + Counter firstCounter = counterProvider.get("first.value"); + firstCounter.increment(); + + assertThat(registry.find("my.counter").counter()).isEqualTo(firstCounter); + assertThat(firstCounter.count()).isEqualTo(1); + + Counter sameCounter = counterProvider.get("first.value"); + sameCounter.increment(); + + assertThat(registry.getMeters()).hasSize(1); + assertThat(sameCounter.count()).isEqualTo(2); + + Counter secondCounter = counterProvider.get("second.value"); + secondCounter.increment(); + + assertThat(registry.getMeters()).hasSize(2); + assertThat(secondCounter.count()).isEqualTo(1); + } + + @Test + void provideTimer() { + Meter.Provider timerProvider = Timer.builder("my.timer") + .register(registry, value -> value == null ? Tags.empty() : Tags.of("my.key", value)); + + assertThat(registry.getMeters()).hasSize(0); + + Timer firstTimer = timerProvider.get("first.value"); + firstTimer.record(1, TimeUnit.NANOSECONDS); + + assertThat(registry.find("my.timer").timer()).isEqualTo(firstTimer); + assertThat(firstTimer.count()).isEqualTo(1); + + Timer sameTimer = timerProvider.get("first.value"); + sameTimer.record(2, TimeUnit.NANOSECONDS); + + assertThat(registry.getMeters()).hasSize(1); + assertThat(sameTimer.count()).isEqualTo(2); + + Timer secondTimer = timerProvider.get("second.value"); + secondTimer.record(4, TimeUnit.NANOSECONDS); + + assertThat(registry.getMeters()).hasSize(2); + assertThat(secondTimer.count()).isEqualTo(1); + } + + @Test + void provideSummary() { + Meter.Provider summaryProvider = DistributionSummary.builder("my.summary") + .register(registry, value -> value == null ? Tags.empty() : Tags.of("my.key", value)); + + assertThat(registry.getMeters()).hasSize(0); + + DistributionSummary firstSummary = summaryProvider.get("first.value"); + firstSummary.record(1); + + assertThat(registry.find("my.summary").summary()).isEqualTo(firstSummary); + assertThat(firstSummary.count()).isEqualTo(1); + + DistributionSummary sameSummary = summaryProvider.get("first.value"); + sameSummary.record(2); + + assertThat(registry.getMeters()).hasSize(1); + assertThat(sameSummary.count()).isEqualTo(2); + + DistributionSummary secondSummary = summaryProvider.get("second.value"); + secondSummary.record(4); + + assertThat(registry.getMeters()).hasSize(2); + assertThat(secondSummary.count()).isEqualTo(1); + } + + @Test + void provideGauge() { + Meter.Provider gaugeProvider = Gauge + .builder("my.gauge", null, AtomicInteger::doubleValue) + .register(registry, AtomicInteger::new, value -> value == null ? Tags.empty() : Tags.of("my.key", value)); + + assertThat(registry.getMeters()).hasSize(0); + + AtomicInteger firstGauge = gaugeProvider.get("first.value"); + firstGauge.set(1); + + assertThat(firstGauge.get()).isEqualTo(1); + + AtomicInteger sameGauge = gaugeProvider.get("first.value"); + + assertThat(registry.getMeters()).hasSize(1); + assertThat(sameGauge).isEqualTo(firstGauge); + + AtomicInteger secondGauge = gaugeProvider.get("second.value"); + + assertThat(registry.getMeters()).hasSize(2); + assertThat(secondGauge).isNotEqualTo(firstGauge); + } + +} From 76538718c7f825c11e7e5e9fed06313849974487 Mon Sep 17 00:00:00 2001 From: qweek Date: Fri, 22 Sep 2023 05:35:21 +0800 Subject: [PATCH 2/3] add license to MeterCacheTest --- .../core/instrument/MeterCacheTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java index 180af722f2..767f4b9845 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 VMware, Inc. + * + * Licensed 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 + * + * https://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 io.micrometer.core.instrument; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; From 719940d2e077314eddffa61aaac439bcd47ff47e Mon Sep 17 00:00:00 2001 From: qweek Date: Fri, 22 Sep 2023 13:11:53 +0800 Subject: [PATCH 3/3] add test case to MeterCacheTest to check tags immutability --- .../core/instrument/MeterCacheTest.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java b/micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java index 767f4b9845..a1735fafb1 100644 --- a/micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java +++ b/micrometer-core/src/test/java/io/micrometer/core/instrument/MeterCacheTest.java @@ -134,4 +134,34 @@ void provideGauge() { assertThat(secondGauge).isNotEqualTo(firstGauge); } + @Test + void provideCounterWithoutDuplicateTags() { + Meter.Provider counterProvider = Counter.builder("my.counter") + .tag("my.default.tag", "my.default.value") + .register(registry, value -> value == null ? Tags.empty() : Tags.of("my.key", value)); + + assertThat(registry.getMeters()).hasSize(0); + + Counter firstCounter = counterProvider.get("first.value"); + firstCounter.increment(); + + assertThat(firstCounter.getId().getTags()).hasSize(2); + assertThat(firstCounter.getId().getTags()).contains(Tag.of("my.default.tag", "my.default.value"), + Tag.of("my.key", "first.value")); + + Counter sameCounter = counterProvider.get("first.value"); + sameCounter.increment(); + + assertThat(sameCounter.getId().getTags()).hasSize(2); + assertThat(sameCounter.getId().getTags()).contains(Tag.of("my.default.tag", "my.default.value"), + Tag.of("my.key", "first.value")); + + Counter secondCounter = counterProvider.get("second.value"); + secondCounter.increment(); + + assertThat(secondCounter.getId().getTags()).hasSize(2); + assertThat(secondCounter.getId().getTags()).contains(Tag.of("my.default.tag", "my.default.value"), + Tag.of("my.key", "second.value")); + } + }