Skip to content

Commit

Permalink
Add shortcuts to assign dynamic tags to Meters (#4097)
Browse files Browse the repository at this point in the history
Closes gh-535
See gh-4092

Co-authored-by: qweek <alnovoselov@mail.ru>
  • Loading branch information
jonatan-ivanov and qweek committed Oct 9, 2023
1 parent 02968ef commit f61a4fa
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
* 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 +120,20 @@ public Builder baseUnit(@Nullable String unit) {
return this;
}

/**
* Convenience method to create 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 MeterProvider}.
* @param registry A registry to add the meter to, if it doesn't already exist.
* @return A {@link MeterProvider} that returns a meter based on the provided
* tags.
* @since 1.12.0
*/
public MeterProvider<Counter> withRegistry(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 +143,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 @@ -30,6 +30,7 @@
* requests hitting an http server.
*
* @author Jon Schneider
* @author Jonatan Ivanov
*/
public interface DistributionSummary extends Meter, HistogramSupport {

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

/**
* Convenience method to create 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 MeterProvider}.
* @param registry A registry to add the meter to, if it doesn't already exist.
* @return A {@link MeterProvider} that returns a meter based on the provided
* tags.
* @since 1.12.0
*/
public MeterProvider<DistributionSummary> withRegistry(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 +410,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,20 @@ public Builder publishPercentileHistogram(@Nullable Boolean enabled) {
return this;
}

/**
* Convenience method to create 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 MeterProvider}.
* @param registry A registry to add the meter to, if it doesn't already exist.
* @return A {@link MeterProvider} that returns a meter based on the provided
* tags.
* @since 1.12.0
*/
public MeterProvider<LongTaskTimer> withRegistry(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 +497,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 @@ -35,6 +35,7 @@
* A named and dimensioned producer of one or more measurements.
*
* @author Jon Schneider
* @author Jonatan Ivanov
*/
public interface Meter {

Expand Down Expand Up @@ -480,6 +481,47 @@ public Meter register(MeterRegistry registry) {

}

/**
* Convenience interface to create new meters from tags based on a common
* "template"/builder. See usage in Meter implementations, e.g.: {@code Timer},
* {@code Counter}
*
* @param <T> Meter type
* @since 1.12.0
*/
interface MeterProvider<T extends Meter> {

/**
* Registers (creates a new or gets an existing one if already exists) Meters
* using the provided tags.
* @param tags Tags to attach to the Meter about to be registered
* @return A new or existing Meter
*/
T withTags(Iterable<? extends Tag> tags);

/**
* Registers (creates a new or gets an existing one if already exists) Meters
* using the provided tags.
* @param tags Tags to attach to the Meter about to be registered
* @return A new or existing Meter
*/
default T withTags(String... tags) {
return withTags(Tags.of(tags));
}

/**
* Registers (creates a new or gets an existing one if already exists) Meters
* using the provided tags.
* @param key the tag key to add
* @param value the tag value to add
* @return A new or existing Meter
*/
default T withTag(String key, String value) {
return withTags(Tags.of(key, value));
}

}

default void close() {
}

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,20 @@ public Builder description(String description) {
return super.description(description);
}

/**
* Convenience method to create 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 MeterProvider}.
* @param registry A registry to add the meter to, if it doesn't already exist.
* @return A {@link MeterProvider} that returns a meter based on the provided
* tags.
* @since 1.12.0
*/
public MeterProvider<Timer> withRegistry(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 +450,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,148 @@
/*
* 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.Meter.MeterProvider;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.time.Duration;

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() {
MeterProvider<Counter> counterProvider = Counter.builder("test.counter")
.tag("static", "abc")
.withRegistry(registry);

counterProvider.withTags(Tags.of("dynamic", "1")).increment();
counterProvider.withTags("dynamic", "2").increment();
counterProvider.withTag("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() {
MeterProvider<Counter> counterProvider = Counter.builder("test.counter")
.tag("static", "abc")
.withRegistry(registry);

counterProvider.withTags(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() {
MeterProvider<Timer> timerProvider = Timer.builder("test.timer").tag("static", "abc").withRegistry(registry);

timerProvider.withTags(Tags.of("dynamic", "1")).record(Duration.ofMillis(100));
timerProvider.withTags("dynamic", "2").record(Duration.ofMillis(200));
timerProvider.withTag("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() {
MeterProvider<Timer> timerProvider = Timer.builder("test.timer").tag("static", "abc").withRegistry(registry);

timerProvider.withTags(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() {
MeterProvider<LongTaskTimer> timeProvider = LongTaskTimer.builder("test.active.timer")
.tag("static", "abc")
.withRegistry(registry);

timeProvider.withTags(Tags.of("dynamic", "1")).start().stop();
timeProvider.withTags("dynamic", "2").start().stop();
timeProvider.withTag("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() {
MeterProvider<LongTaskTimer> timeProvider = LongTaskTimer.builder("test.active.timer")
.tag("static", "abc")
.withRegistry(registry);

timeProvider.withTags(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() {
MeterProvider<DistributionSummary> distributionProvider = DistributionSummary.builder("test.distribution")
.tag("static", "abc")
.withRegistry(registry);

distributionProvider.withTags(Tags.of("dynamic", "1")).record(1);
distributionProvider.withTags("dynamic", "2").record(2);
distributionProvider.withTag("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() {
MeterProvider<DistributionSummary> distributionProvider = DistributionSummary.builder("test.distribution")
.tag("static", "abc")
.withRegistry(registry);

distributionProvider.withTags(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 f61a4fa

Please sign in to comment.