Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add method to SimpleMeterRegistry for outputting all meters #2946

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package io.micrometer.core.instrument.simple;

import io.micrometer.core.annotation.Incubating;
import io.micrometer.core.instrument.*;
import io.micrometer.core.instrument.cumulative.*;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
Expand All @@ -26,9 +27,12 @@
import io.micrometer.core.instrument.step.*;
import io.micrometer.core.lang.Nullable;

import java.util.Comparator;
import java.util.concurrent.TimeUnit;
import java.util.function.ToDoubleFunction;
import java.util.function.ToLongFunction;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
* A minimal meter registry implementation primarily used for tests.
Expand Down Expand Up @@ -159,4 +163,41 @@ protected DistributionStatisticConfig defaultHistogramConfig() {
.build()
.merge(DistributionStatisticConfig.DEFAULT);
}

/**
* A very simple implementation that tries to represent the contents of the registry.
* The output is meant to be readable by humans, please do not parse it programmatically because the format can change.
*
* @return text representation of the meters in the registry
*/
@Incubating(since = "2022-01-06")
public String getMetersAsString() {
return this.getMeters().stream()
.sorted(Comparator.comparing(meter -> meter.getId().getName()))
.map(this::toString)
.collect(Collectors.joining("\n"));
}

private String toString(Meter meter) {
String name = meter.getId().getName();
Meter.Type type = meter.getId().getType();
String baseUnit = meter.getId().getBaseUnit();
String tags = meter.getId().getTags().stream()
.map(this::toString)
.collect(Collectors.joining(", "));

return StreamSupport.stream(meter.measure().spliterator(), false)
.map(measurement -> toString(name, type, baseUnit, tags, measurement))
.collect(Collectors.joining("\n"));
}

private String toString(Tag tag) {
return String.format("%s='%s'", tag.getKey(), tag.getValue());
}

private String toString(String name, Meter.Type type, @Nullable String baseUnit, String tags, Measurement measurement) {
String statistic = measurement.getStatistic().toString().toLowerCase();
String unit = baseUnit != null ? "." + baseUnit : "";
return String.format("%s.%s%s(%s)[%s] %s", name, statistic, unit, type, tags, measurement.getValue());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,18 @@
package io.micrometer.core.instrument.simple;

import io.micrometer.core.Issue;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.FunctionCounter;
import io.micrometer.core.instrument.FunctionTimer;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.LongTaskTimer;
import io.micrometer.core.instrument.Measurement;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MockClock;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.TimeGauge;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.cumulative.CumulativeFunctionCounter;
import io.micrometer.core.instrument.cumulative.CumulativeFunctionTimer;
Expand All @@ -31,8 +36,12 @@
import org.junit.jupiter.api.Test;

import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.assertj.core.api.Assertions.assertThat;

/**
Expand All @@ -55,7 +64,7 @@ void serviceLevelObjectivesOnlyNoPercentileHistogram() {
summary.record(1);

Timer timer = Timer.builder("my.timer").serviceLevelObjectives(Duration.ofMillis(1)).register(registry);
timer.record(1, TimeUnit.MILLISECONDS);
timer.record(1, MILLISECONDS);

Gauge summaryHist1 = registry.get("my.summary.histogram").tags("le", "1").gauge();
Gauge summaryHist2 = registry.get("my.summary.histogram").tags("le", "2").gauge();
Expand Down Expand Up @@ -104,6 +113,85 @@ void newFunctionCounterWhenCountingModeIsStepShouldReturnStepFunctionCounter() {
assertThat(functionCounter).isInstanceOf(StepFunctionCounter.class);
}

@Test
void stringRepresentationOfMetersShouldBeOk() {
MockClock clock = new MockClock();
SimpleMeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock);

AtomicInteger temperature = new AtomicInteger(24);
Gauge.builder("temperature", () -> temperature)
.baseUnit("celsius")
.register(registry);

Counter correctAnswers = Counter.builder("answers")
.tag("correct", "true")
.register(registry);
correctAnswers.increment();
correctAnswers.increment();

Counter incorrectAnswers = Counter.builder("answers")
.tag("correct", "false")
.register(registry);
incorrectAnswers.increment();

Timer latency = Timer.builder("latency")
.tag("service", "test")
.tag("method", "GET")
.tag("uri", "/api/people")
.register(registry);

DistributionSummary requestSize = DistributionSummary.builder("request.size")
.baseUnit("bytes")
.register(registry);

for (int i = 0; i < 10; i++) {
latency.record(Duration.ofMillis(20 + i * 2));
requestSize.record(100 + i * 10);
}

LongTaskTimer handler = LongTaskTimer.builder("handler").register(registry);
LongTaskTimer.Sample sample = handler.start();
clock.add(Duration.ofSeconds(3));

AtomicLong processingTime = new AtomicLong(300);
TimeGauge.builder("processing.time", () -> processingTime, MILLISECONDS).register(registry);

AtomicInteger cacheMisses = new AtomicInteger(42);
FunctionCounter.builder("cache.miss", cacheMisses, AtomicInteger::doubleValue).register(registry);

AtomicLong cacheLatency = new AtomicLong(100);
FunctionTimer.builder("cache.latency", cacheLatency, obj -> 5, AtomicLong::doubleValue, MILLISECONDS).register(registry);

Meter.builder(
"custom.meter",
Meter.Type.OTHER,
Arrays.asList(
new Measurement(() -> 42d, Statistic.VALUE),
new Measurement(() -> 21d, Statistic.UNKNOWN)
)
).register(registry);

assertThat(registry.getMetersAsString()).isEqualTo(""
+ "answers.count(COUNTER)[correct='true'] 2.0\n"
+ "answers.count(COUNTER)[correct='false'] 1.0\n"
+ "cache.latency.count.seconds(TIMER)[] 5.0\n"
+ "cache.latency.total_time.seconds(TIMER)[] 0.1\n"
+ "cache.miss.count(COUNTER)[] 42.0\n"
+ "custom.meter.value(OTHER)[] 42.0\n"
+ "custom.meter.unknown(OTHER)[] 21.0\n"
+ "handler.active_tasks.seconds(LONG_TASK_TIMER)[] 1.0\n"
+ "handler.duration.seconds(LONG_TASK_TIMER)[] 3.0\n"
+ "latency.count.seconds(TIMER)[method='GET', service='test', uri='/api/people'] 10.0\n"
+ "latency.total_time.seconds(TIMER)[method='GET', service='test', uri='/api/people'] 0.29\n"
+ "latency.max.seconds(TIMER)[method='GET', service='test', uri='/api/people'] 0.038\n"
+ "processing.time.value.seconds(GAUGE)[] 0.3\n"
+ "request.size.count.bytes(DISTRIBUTION_SUMMARY)[] 10.0\n"
+ "request.size.total.bytes(DISTRIBUTION_SUMMARY)[] 1450.0\n"
+ "request.size.max.bytes(DISTRIBUTION_SUMMARY)[] 190.0\n"
+ "temperature.value.celsius(GAUGE)[] 24.0");
sample.stop();
}

private SimpleMeterRegistry createRegistry(CountingMode mode) {
return new SimpleMeterRegistry(new SimpleConfig() {

Expand Down