Support composite registries in MetricsEndpoint
Update `MetricsEndpoint` to deal with `CompositeMeterRegistry` instances. Closes gh-10535
This commit is contained in:
parent
b3555fa5c5
commit
37975836f0
|
|
@ -16,11 +16,14 @@
|
||||||
|
|
||||||
package org.springframework.boot.actuate.metrics;
|
package org.springframework.boot.actuate.metrics;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
|
@ -29,6 +32,7 @@ import io.micrometer.core.instrument.Meter;
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
import io.micrometer.core.instrument.Statistic;
|
import io.micrometer.core.instrument.Statistic;
|
||||||
import io.micrometer.core.instrument.Tag;
|
import io.micrometer.core.instrument.Tag;
|
||||||
|
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
|
||||||
|
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||||
|
|
@ -52,9 +56,22 @@ public class MetricsEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReadOperation
|
@ReadOperation
|
||||||
public Map<String, List<String>> listNames() {
|
public ListNamesResponse listNames() {
|
||||||
return Collections.singletonMap("names", this.registry.getMeters().stream()
|
return new ListNamesResponse(recurseListNames(this.registry));
|
||||||
.map(this::getMeterIdName).distinct().collect(Collectors.toList()));
|
}
|
||||||
|
|
||||||
|
private Set<String> recurseListNames(MeterRegistry registry) {
|
||||||
|
Set<String> names = new HashSet<>();
|
||||||
|
if (registry instanceof CompositeMeterRegistry) {
|
||||||
|
for (MeterRegistry compositeMember : ((CompositeMeterRegistry) registry)
|
||||||
|
.getRegistries()) {
|
||||||
|
names.addAll(recurseListNames(compositeMember));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
registry.getMeters().stream().map(this::getMeterIdName).forEach(names::add);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getMeterIdName(Meter meter) {
|
private String getMeterIdName(Meter meter) {
|
||||||
|
|
@ -62,13 +79,12 @@ public class MetricsEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReadOperation
|
@ReadOperation
|
||||||
public Response metric(@Selector String requiredMetricName,
|
public MetricResponse metric(@Selector String requiredMetricName,
|
||||||
@Nullable List<String> tag) {
|
@Nullable List<String> tag) {
|
||||||
Assert.isTrue(tag == null || tag.stream().allMatch((t) -> t.contains(":")),
|
Assert.isTrue(tag == null || tag.stream().allMatch((t) -> t.contains(":")),
|
||||||
"Each tag parameter must be in the form key:value");
|
"Each tag parameter must be in the form key:value");
|
||||||
List<Tag> tags = parseTags(tag);
|
List<Tag> tags = parseTags(tag);
|
||||||
Collection<Meter> meters = this.registry.find(requiredMetricName).tags(tags)
|
Collection<Meter> meters = recurseFindMeter(this.registry, requiredMetricName, tags);
|
||||||
.meters();
|
|
||||||
if (meters.isEmpty()) {
|
if (meters.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -90,18 +106,32 @@ public class MetricsEndpoint {
|
||||||
|
|
||||||
tags.forEach((t) -> availableTags.remove(t.getKey()));
|
tags.forEach((t) -> availableTags.remove(t.getKey()));
|
||||||
|
|
||||||
return new Response(requiredMetricName,
|
return new MetricResponse(requiredMetricName,
|
||||||
samples.entrySet().stream()
|
samples.entrySet().stream()
|
||||||
.map((sample) -> new Response.Sample(sample.getKey(),
|
.map((sample) -> new MetricResponse.Sample(sample.getKey(),
|
||||||
sample.getValue()))
|
sample.getValue()))
|
||||||
.collect(
|
.collect(Collectors.toList()),
|
||||||
Collectors.toList()),
|
|
||||||
availableTags.entrySet().stream()
|
availableTags.entrySet().stream()
|
||||||
.map((tagValues) -> new Response.AvailableTag(tagValues.getKey(),
|
.map((tagValues) -> new MetricResponse.AvailableTag(
|
||||||
tagValues.getValue()))
|
tagValues.getKey(), tagValues.getValue()))
|
||||||
.collect(Collectors.toList()));
|
.collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Collection<Meter> recurseFindMeter(MeterRegistry registry, String name,
|
||||||
|
Iterable<Tag> tags) {
|
||||||
|
Collection<Meter> meters = new ArrayList<>();
|
||||||
|
if (registry instanceof CompositeMeterRegistry) {
|
||||||
|
for (MeterRegistry compositeMember : ((CompositeMeterRegistry) registry)
|
||||||
|
.getRegistries()) {
|
||||||
|
meters.addAll(recurseFindMeter(compositeMember, name, tags));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
meters.addAll(registry.find(name).tags(tags).meters());
|
||||||
|
}
|
||||||
|
return meters;
|
||||||
|
}
|
||||||
|
|
||||||
private List<Tag> parseTags(List<String> tags) {
|
private List<Tag> parseTags(List<String> tags) {
|
||||||
return tags == null ? Collections.emptyList() : tags.stream().map((t) -> {
|
return tags == null ? Collections.emptyList() : tags.stream().map((t) -> {
|
||||||
String[] tagParts = t.split(":", 2);
|
String[] tagParts = t.split(":", 2);
|
||||||
|
|
@ -110,9 +140,25 @@ public class MetricsEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response payload.
|
* Response payload for a metric name listing.
|
||||||
*/
|
*/
|
||||||
static class Response {
|
static class ListNamesResponse {
|
||||||
|
|
||||||
|
private final Set<String> names;
|
||||||
|
|
||||||
|
ListNamesResponse(Set<String> names) {
|
||||||
|
this.names = names;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<String> getNames() {
|
||||||
|
return this.names;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response payload for a metric name selector.
|
||||||
|
*/
|
||||||
|
static class MetricResponse {
|
||||||
|
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
|
|
@ -120,7 +166,7 @@ public class MetricsEndpoint {
|
||||||
|
|
||||||
private final List<AvailableTag> availableTags;
|
private final List<AvailableTag> availableTags;
|
||||||
|
|
||||||
Response(String name, List<Sample> measurements,
|
MetricResponse(String name, List<Sample> measurements,
|
||||||
List<AvailableTag> availableTags) {
|
List<AvailableTag> availableTags) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.measurements = measurements;
|
this.measurements = measurements;
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,12 @@
|
||||||
package org.springframework.boot.actuate.metrics;
|
package org.springframework.boot.actuate.metrics;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
import io.micrometer.core.instrument.Statistic;
|
import io.micrometer.core.instrument.Statistic;
|
||||||
|
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
|
||||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
|
@ -43,9 +42,8 @@ public class MetricsEndpointTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void listNamesHandlesEmptyListOfMeters() {
|
public void listNamesHandlesEmptyListOfMeters() {
|
||||||
Map<String, List<String>> result = this.endpoint.listNames();
|
MetricsEndpoint.ListNamesResponse result = this.endpoint.listNames();
|
||||||
assertThat(result).containsOnlyKeys("names");
|
assertThat(result.getNames()).isEmpty();
|
||||||
assertThat(result.get("names")).isEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -53,18 +51,32 @@ public class MetricsEndpointTests {
|
||||||
this.registry.counter("com.example.foo");
|
this.registry.counter("com.example.foo");
|
||||||
this.registry.counter("com.example.bar");
|
this.registry.counter("com.example.bar");
|
||||||
this.registry.counter("com.example.foo");
|
this.registry.counter("com.example.foo");
|
||||||
Map<String, List<String>> result = this.endpoint.listNames();
|
MetricsEndpoint.ListNamesResponse result = this.endpoint.listNames();
|
||||||
assertThat(result).containsOnlyKeys("names");
|
assertThat(result.getNames()).containsOnlyOnce("com.example.foo",
|
||||||
assertThat(result.get("names")).containsOnlyOnce("com.example.foo",
|
|
||||||
"com.example.bar");
|
"com.example.bar");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void listNamesRecursesOverCompositeRegistries() {
|
||||||
|
CompositeMeterRegistry composite = new CompositeMeterRegistry();
|
||||||
|
SimpleMeterRegistry reg1 = new SimpleMeterRegistry();
|
||||||
|
SimpleMeterRegistry reg2 = new SimpleMeterRegistry();
|
||||||
|
composite.add(reg1);
|
||||||
|
composite.add(reg2);
|
||||||
|
|
||||||
|
reg1.counter("counter1").increment();
|
||||||
|
reg2.counter("counter2").increment();
|
||||||
|
|
||||||
|
MetricsEndpoint endpoint = new MetricsEndpoint(composite);
|
||||||
|
assertThat(endpoint.listNames().getNames()).containsOnly("counter1", "counter2");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void metricValuesAreTheSumOfAllTimeSeriesMatchingTags() {
|
public void metricValuesAreTheSumOfAllTimeSeriesMatchingTags() {
|
||||||
this.registry.counter("cache", "result", "hit", "host", "1").increment(2);
|
this.registry.counter("cache", "result", "hit", "host", "1").increment(2);
|
||||||
this.registry.counter("cache", "result", "miss", "host", "1").increment(2);
|
this.registry.counter("cache", "result", "miss", "host", "1").increment(2);
|
||||||
this.registry.counter("cache", "result", "hit", "host", "2").increment(2);
|
this.registry.counter("cache", "result", "hit", "host", "2").increment(2);
|
||||||
MetricsEndpoint.Response response = this.endpoint.metric("cache",
|
MetricsEndpoint.MetricResponse response = this.endpoint.metric("cache",
|
||||||
Collections.emptyList());
|
Collections.emptyList());
|
||||||
assertThat(response.getName()).isEqualTo("cache");
|
assertThat(response.getName()).isEqualTo("cache");
|
||||||
assertThat(availableTagKeys(response)).containsExactly("result", "host");
|
assertThat(availableTagKeys(response)).containsExactly("result", "host");
|
||||||
|
|
@ -77,29 +89,45 @@ public class MetricsEndpointTests {
|
||||||
@Test
|
@Test
|
||||||
public void metricWithSpaceInTagValue() {
|
public void metricWithSpaceInTagValue() {
|
||||||
this.registry.counter("counter", "key", "a space").increment(2);
|
this.registry.counter("counter", "key", "a space").increment(2);
|
||||||
MetricsEndpoint.Response response = this.endpoint.metric("counter",
|
MetricsEndpoint.MetricResponse response = this.endpoint.metric("counter",
|
||||||
Collections.singletonList("key:a space"));
|
Collections.singletonList("key:a space"));
|
||||||
assertThat(response.getName()).isEqualTo("counter");
|
assertThat(response.getName()).isEqualTo("counter");
|
||||||
assertThat(availableTagKeys(response)).isEmpty();
|
assertThat(availableTagKeys(response)).isEmpty();
|
||||||
assertThat(getCount(response)).hasValue(2.0);
|
assertThat(getCount(response)).hasValue(2.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void metricPresentInOneRegistryOfACompositeAndNotAnother() {
|
||||||
|
CompositeMeterRegistry composite = new CompositeMeterRegistry();
|
||||||
|
SimpleMeterRegistry reg1 = new SimpleMeterRegistry();
|
||||||
|
SimpleMeterRegistry reg2 = new SimpleMeterRegistry();
|
||||||
|
composite.add(reg1);
|
||||||
|
composite.add(reg2);
|
||||||
|
|
||||||
|
reg1.counter("counter1").increment();
|
||||||
|
reg2.counter("counter2").increment();
|
||||||
|
|
||||||
|
MetricsEndpoint endpoint = new MetricsEndpoint(composite);
|
||||||
|
assertThat(endpoint.metric("counter1", Collections.emptyList())).isNotNull();
|
||||||
|
assertThat(endpoint.metric("counter2", Collections.emptyList())).isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void nonExistentMetric() {
|
public void nonExistentMetric() {
|
||||||
MetricsEndpoint.Response response = this.endpoint.metric("does.not.exist",
|
MetricsEndpoint.MetricResponse response = this.endpoint.metric("does.not.exist",
|
||||||
Collections.emptyList());
|
Collections.emptyList());
|
||||||
assertThat(response).isNull();
|
assertThat(response).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<Double> getCount(MetricsEndpoint.Response response) {
|
private Optional<Double> getCount(MetricsEndpoint.MetricResponse response) {
|
||||||
return response.getMeasurements().stream()
|
return response.getMeasurements().stream()
|
||||||
.filter((ms) -> ms.getStatistic().equals(Statistic.Count)).findAny()
|
.filter((ms) -> ms.getStatistic().equals(Statistic.Count)).findAny()
|
||||||
.map(MetricsEndpoint.Response.Sample::getValue);
|
.map(MetricsEndpoint.MetricResponse.Sample::getValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream<String> availableTagKeys(MetricsEndpoint.Response response) {
|
private Stream<String> availableTagKeys(MetricsEndpoint.MetricResponse response) {
|
||||||
return response.getAvailableTags().stream()
|
return response.getAvailableTags().stream()
|
||||||
.map(MetricsEndpoint.Response.AvailableTag::getTag);
|
.map(MetricsEndpoint.MetricResponse.AvailableTag::getTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue