Skip to content

Commit

Permalink
Add shortcuts to assign dynamic tags to Meters
Browse files Browse the repository at this point in the history
Closes micrometer-metricsgh-535
See micrometer-metricsgh-4092

Co-authored-by: qweek <alnovoselov@mail.ru>
  • Loading branch information
jonatan-ivanov and qweek committed Oct 6, 2023
1 parent 3d42c8b commit da8dfd0
Show file tree
Hide file tree
Showing 5 changed files with 216 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@
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
* lesser value. If you need to track a value that goes up and down, use a {@link Gauge}.
*
* @author Jon Schneider
* @author Jonatan Ivanov
*/
public interface Counter extends Meter {

Expand Down Expand Up @@ -119,6 +121,18 @@ public Builder baseUnit(@Nullable String unit) {
return this;
}

/**
* Convenience method to create new meters from the builder that only differ in
* tags. This method can be used for dynamic tagging by creating the builder once
* and applying the dynamically changing tags using the returned {@link Function}.
* @param registry A registry to add the meter to, if it doesn't already exist.
* @return A {@link Function} that returns a meter based on the provided tags.
* @since 1.12.0
*/
public Function<Tags, Counter> with(MeterRegistry registry) {
return extraTags -> register(registry, tags.and(extraTags));
}

/**
* Add the counter to a single registry, or return an existing counter in that
* registry. The returned counter will be unique for each registry, but each
Expand All @@ -128,6 +142,10 @@ public Builder baseUnit(@Nullable String unit) {
* @return A new or existing counter.
*/
public Counter register(MeterRegistry registry) {
return register(registry, tags);
}

private Counter register(MeterRegistry registry, Tags tags) {
return registry.counter(new Meter.Id(name, tags, baseUnit, description, Type.COUNTER));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@
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
* requests hitting an http server.
*
* @author Jon Schneider
* @author Jonatan Ivanov
*/
public interface DistributionSummary extends Meter, HistogramSupport {

Expand Down Expand Up @@ -385,6 +387,18 @@ public Builder scale(double scale) {
return this;
}

/**
* Convenience method to create new meters from the builder that only differ in
* tags. This method can be used for dynamic tagging by creating the builder once
* and applying the dynamically changing tags using the returned {@link Function}.
* @param registry A registry to add the meter to, if it doesn't already exist.
* @return A {@link Function} that returns a meter based on the provided tags.
* @since 1.12.0
*/
public Function<Tags, DistributionSummary> with(MeterRegistry registry) {
return extraTags -> register(registry, tags.and(extraTags));
}

/**
* Add the distribution summary to a single registry, or return an existing
* distribution summary in that registry. The returned distribution summary will
Expand All @@ -395,6 +409,10 @@ public Builder scale(double scale) {
* @return A new or existing distribution summary.
*/
public DistributionSummary register(MeterRegistry registry) {
return register(registry, tags);
}

private DistributionSummary register(MeterRegistry registry, Tags tags) {
return registry.summary(new Meter.Id(name, tags, baseUnit, description, Type.DISTRIBUTION_SUMMARY),
distributionConfigBuilder.build(), scale);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,14 @@
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.DoubleSupplier;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import java.util.function.*;

/**
* A long task timer is used to track the total duration of all in-flight long-running
* tasks and the number of such tasks.
*
* @author Jon Schneider
* @author Jonatan Ivanov
*/
public interface LongTaskTimer extends Meter, HistogramSupport {

Expand Down Expand Up @@ -477,6 +473,18 @@ public Builder publishPercentileHistogram(@Nullable Boolean enabled) {
return this;
}

/**
* Convenience method to create new meters from the builder that only differ in
* tags. This method can be used for dynamic tagging by creating the builder once
* and applying the dynamically changing tags using the returned {@link Function}.
* @param registry A registry to add the meter to, if it doesn't already exist.
* @return A {@link Function} that returns a meter based on the provided tags.
* @since 1.12.0
*/
public Function<Tags, LongTaskTimer> with(MeterRegistry registry) {
return extraTags -> register(registry, tags.and(extraTags));
}

/**
* Add the long task timer to a single registry, or return an existing long task
* timer in that registry. The returned long task timer will be unique for each
Expand All @@ -487,6 +495,10 @@ public Builder publishPercentileHistogram(@Nullable Boolean enabled) {
* @return A new or existing long task timer.
*/
public LongTaskTimer register(MeterRegistry registry) {
return register(registry, tags);
}

private LongTaskTimer register(MeterRegistry registry, Tags tags) {
return registry.more()
.longTaskTimer(new Meter.Id(name, tags, null, description, Type.LONG_TASK_TIMER),
distributionConfigBuilder.build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,7 @@
import java.util.Arrays;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;
import java.util.function.*;

/**
* Timer intended to track of a large number of short running events. Example would be
Expand All @@ -40,6 +36,7 @@
*
* @author Jon Schneider
* @author Oleksii Bondar
* @author Jonatan Ivanov
*/
public interface Timer extends Meter, HistogramSupport {

Expand Down Expand Up @@ -430,6 +427,18 @@ public Builder description(String description) {
return super.description(description);
}

/**
* Convenience method to create new meters from the builder that only differ in
* tags. This method can be used for dynamic tagging by creating the builder once
* and applying the dynamically changing tags using the returned {@link Function}.
* @param registry A registry to add the meter to, if it doesn't already exist.
* @return A {@link Function} that returns a meter based on the provided tags.
* @since 1.12.0
*/
public Function<Tags, Timer> with(MeterRegistry registry) {
return extraTags -> register(registry, tags.and(extraTags));
}

/**
* Add the timer to a single registry, or return an existing timer in that
* registry. The returned timer will be unique for each registry, but each
Expand All @@ -439,6 +448,10 @@ public Builder description(String description) {
* @return A new or existing timer.
*/
public Timer register(MeterRegistry registry) {
return register(registry, tags);
}

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),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* 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;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.util.function.Function;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for convenience methods for dynamic tagging.
*
* @author Jonatan Ivanov
*/
class DynamicTagsTests {

private MeterRegistry registry;

@BeforeEach
void setUp() {
registry = new SimpleMeterRegistry();
}

@Test
void shouldCreateCountersDynamically() {
Function<Tags, Counter> counterFactory = Counter.builder("test.counter").tag("static", "abc").with(registry);

counterFactory.apply(Tags.of("dynamic", "1")).increment();
counterFactory.apply(Tags.of("dynamic", "2")).increment();
counterFactory.apply(Tags.of("dynamic", "1")).increment();

assertThat(registry.getMeters()).hasSize(2);
assertThat(registry.find("test.counter").tags("static", "abc", "dynamic", "1").counters()).hasSize(1);
assertThat(registry.find("test.counter").tags("static", "abc", "dynamic", "2").counters()).hasSize(1);
}

@Test
void shouldOverrideStaticTagsWhenCreatesCountersDynamically() {
Function<Tags, Counter> counterFactory = Counter.builder("test.counter").tag("static", "abc").with(registry);

counterFactory.apply(Tags.of("static", "xyz", "dynamic", "1")).increment();

assertThat(registry.getMeters()).hasSize(1);
assertThat(registry.find("test.counter").tags("static", "xyz", "dynamic", "1").counters()).hasSize(1);
}

@Test
void shouldCreateTimersDynamically() {
Function<Tags, Timer> timerFactory = Timer.builder("test.timer").tag("static", "abc").with(registry);

timerFactory.apply(Tags.of("dynamic", "1")).record(Duration.ofMillis(100));
timerFactory.apply(Tags.of("dynamic", "2")).record(Duration.ofMillis(200));
timerFactory.apply(Tags.of("dynamic", "1")).record(Duration.ofMillis(100));

assertThat(registry.getMeters()).hasSize(2);
assertThat(registry.find("test.timer").tags("static", "abc", "dynamic", "1").timers()).hasSize(1);
assertThat(registry.find("test.timer").tags("static", "abc", "dynamic", "2").timers()).hasSize(1);
}

@Test
void shouldOverrideStaticTagsWhenCreatesTimersDynamically() {
Function<Tags, Timer> timerFactory = Timer.builder("test.timer").tag("static", "abc").with(registry);

timerFactory.apply(Tags.of("static", "xyz", "dynamic", "1")).record(Duration.ofMillis(100));

assertThat(registry.getMeters()).hasSize(1);
assertThat(registry.find("test.timer").tags("static", "xyz", "dynamic", "1").timers()).hasSize(1);
}

@Test
void shouldCreateLongTaskTimersDynamically() {
Function<Tags, LongTaskTimer> timerFactory = LongTaskTimer.builder("test.active.timer")
.tag("static", "abc")
.with(registry);

timerFactory.apply(Tags.of("dynamic", "1")).start().stop();
timerFactory.apply(Tags.of("dynamic", "2")).start().stop();
timerFactory.apply(Tags.of("dynamic", "1")).start().stop();

assertThat(registry.getMeters()).hasSize(2);
assertThat(registry.find("test.active.timer").tags("static", "abc", "dynamic", "1").longTaskTimers())
.hasSize(1);
assertThat(registry.find("test.active.timer").tags("static", "abc", "dynamic", "2").longTaskTimers())
.hasSize(1);
}

@Test
void shouldOverrideStaticTagsWhenCreatesLongTaskTimersDynamically() {
Function<Tags, LongTaskTimer> timerFactory = LongTaskTimer.builder("test.active.timer")
.tag("static", "abc")
.with(registry);

timerFactory.apply(Tags.of("static", "xyz", "dynamic", "1")).start().stop();

assertThat(registry.getMeters()).hasSize(1);
assertThat(registry.find("test.active.timer").tags("static", "xyz", "dynamic", "1").longTaskTimers())
.hasSize(1);
}

@Test
void shouldCreateDistributionSummariesDynamically() {
Function<Tags, DistributionSummary> distributionFactory = DistributionSummary.builder("test.distribution")
.tag("static", "abc")
.with(registry);

distributionFactory.apply(Tags.of("dynamic", "1")).record(1);
distributionFactory.apply(Tags.of("dynamic", "2")).record(2);
distributionFactory.apply(Tags.of("dynamic", "1")).record(1);

assertThat(registry.getMeters()).hasSize(2);
assertThat(registry.find("test.distribution").tags("static", "abc", "dynamic", "1").summaries()).hasSize(1);
assertThat(registry.find("test.distribution").tags("static", "abc", "dynamic", "2").summaries()).hasSize(1);
}

@Test
void shouldOverrideStaticTagsWhenCreatesDistributionSummariesDynamically() {
Function<Tags, DistributionSummary> distributionFactory = DistributionSummary.builder("test.distribution")
.tag("static", "abc")
.with(registry);

distributionFactory.apply(Tags.of("static", "xyz", "dynamic", "1")).record(1);

assertThat(registry.getMeters()).hasSize(1);
assertThat(registry.find("test.distribution").tags("static", "xyz", "dynamic", "1").summaries()).hasSize(1);
}

}

0 comments on commit da8dfd0

Please sign in to comment.