Skip to content

Commit 3797583

Browse files
jkschneiderphilwebb
authored andcommitted
Support composite registries in MetricsEndpoint
Update `MetricsEndpoint` to deal with `CompositeMeterRegistry` instances. Closes gh-10535
1 parent b3555fa commit 3797583

File tree

2 files changed

+104
-30
lines changed

2 files changed

+104
-30
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricsEndpoint.java

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@
1616

1717
package org.springframework.boot.actuate.metrics;
1818

19+
import java.util.ArrayList;
1920
import java.util.Collection;
2021
import java.util.Collections;
2122
import java.util.HashMap;
23+
import java.util.HashSet;
2224
import java.util.List;
2325
import java.util.Map;
26+
import java.util.Set;
2427
import java.util.stream.Collectors;
2528
import java.util.stream.Stream;
2629

@@ -29,6 +32,7 @@
2932
import io.micrometer.core.instrument.MeterRegistry;
3033
import io.micrometer.core.instrument.Statistic;
3134
import io.micrometer.core.instrument.Tag;
35+
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
3236

3337
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
3438
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
@@ -52,23 +56,35 @@ public MetricsEndpoint(MeterRegistry registry) {
5256
}
5357

5458
@ReadOperation
55-
public Map<String, List<String>> listNames() {
56-
return Collections.singletonMap("names", this.registry.getMeters().stream()
57-
.map(this::getMeterIdName).distinct().collect(Collectors.toList()));
59+
public ListNamesResponse listNames() {
60+
return new ListNamesResponse(recurseListNames(this.registry));
61+
}
62+
63+
private Set<String> recurseListNames(MeterRegistry registry) {
64+
Set<String> names = new HashSet<>();
65+
if (registry instanceof CompositeMeterRegistry) {
66+
for (MeterRegistry compositeMember : ((CompositeMeterRegistry) registry)
67+
.getRegistries()) {
68+
names.addAll(recurseListNames(compositeMember));
69+
}
70+
}
71+
else {
72+
registry.getMeters().stream().map(this::getMeterIdName).forEach(names::add);
73+
}
74+
return names;
5875
}
5976

6077
private String getMeterIdName(Meter meter) {
6178
return meter.getId().getName();
6279
}
6380

6481
@ReadOperation
65-
public Response metric(@Selector String requiredMetricName,
82+
public MetricResponse metric(@Selector String requiredMetricName,
6683
@Nullable List<String> tag) {
6784
Assert.isTrue(tag == null || tag.stream().allMatch((t) -> t.contains(":")),
6885
"Each tag parameter must be in the form key:value");
6986
List<Tag> tags = parseTags(tag);
70-
Collection<Meter> meters = this.registry.find(requiredMetricName).tags(tags)
71-
.meters();
87+
Collection<Meter> meters = recurseFindMeter(this.registry, requiredMetricName, tags);
7288
if (meters.isEmpty()) {
7389
return null;
7490
}
@@ -90,18 +106,32 @@ public Response metric(@Selector String requiredMetricName,
90106

91107
tags.forEach((t) -> availableTags.remove(t.getKey()));
92108

93-
return new Response(requiredMetricName,
109+
return new MetricResponse(requiredMetricName,
94110
samples.entrySet().stream()
95-
.map((sample) -> new Response.Sample(sample.getKey(),
111+
.map((sample) -> new MetricResponse.Sample(sample.getKey(),
96112
sample.getValue()))
97-
.collect(
98-
Collectors.toList()),
113+
.collect(Collectors.toList()),
99114
availableTags.entrySet().stream()
100-
.map((tagValues) -> new Response.AvailableTag(tagValues.getKey(),
101-
tagValues.getValue()))
115+
.map((tagValues) -> new MetricResponse.AvailableTag(
116+
tagValues.getKey(), tagValues.getValue()))
102117
.collect(Collectors.toList()));
103118
}
104119

120+
private Collection<Meter> recurseFindMeter(MeterRegistry registry, String name,
121+
Iterable<Tag> tags) {
122+
Collection<Meter> meters = new ArrayList<>();
123+
if (registry instanceof CompositeMeterRegistry) {
124+
for (MeterRegistry compositeMember : ((CompositeMeterRegistry) registry)
125+
.getRegistries()) {
126+
meters.addAll(recurseFindMeter(compositeMember, name, tags));
127+
}
128+
}
129+
else {
130+
meters.addAll(registry.find(name).tags(tags).meters());
131+
}
132+
return meters;
133+
}
134+
105135
private List<Tag> parseTags(List<String> tags) {
106136
return tags == null ? Collections.emptyList() : tags.stream().map((t) -> {
107137
String[] tagParts = t.split(":", 2);
@@ -110,17 +140,33 @@ private List<Tag> parseTags(List<String> tags) {
110140
}
111141

112142
/**
113-
* Response payload.
143+
* Response payload for a metric name listing.
144+
*/
145+
static class ListNamesResponse {
146+
147+
private final Set<String> names;
148+
149+
ListNamesResponse(Set<String> names) {
150+
this.names = names;
151+
}
152+
153+
public Set<String> getNames() {
154+
return this.names;
155+
}
156+
}
157+
158+
/**
159+
* Response payload for a metric name selector.
114160
*/
115-
static class Response {
161+
static class MetricResponse {
116162

117163
private final String name;
118164

119165
private final List<Sample> measurements;
120166

121167
private final List<AvailableTag> availableTags;
122168

123-
Response(String name, List<Sample> measurements,
169+
MetricResponse(String name, List<Sample> measurements,
124170
List<AvailableTag> availableTags) {
125171
this.name = name;
126172
this.measurements = measurements;

spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@
1717
package org.springframework.boot.actuate.metrics;
1818

1919
import java.util.Collections;
20-
import java.util.List;
21-
import java.util.Map;
2220
import java.util.Optional;
2321
import java.util.stream.Stream;
2422

2523
import io.micrometer.core.instrument.MeterRegistry;
2624
import io.micrometer.core.instrument.Statistic;
25+
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
2726
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
2827
import org.junit.Test;
2928

@@ -43,28 +42,41 @@ public class MetricsEndpointTests {
4342

4443
@Test
4544
public void listNamesHandlesEmptyListOfMeters() {
46-
Map<String, List<String>> result = this.endpoint.listNames();
47-
assertThat(result).containsOnlyKeys("names");
48-
assertThat(result.get("names")).isEmpty();
45+
MetricsEndpoint.ListNamesResponse result = this.endpoint.listNames();
46+
assertThat(result.getNames()).isEmpty();
4947
}
5048

5149
@Test
5250
public void listNamesProducesListOfUniqueMeterNames() {
5351
this.registry.counter("com.example.foo");
5452
this.registry.counter("com.example.bar");
5553
this.registry.counter("com.example.foo");
56-
Map<String, List<String>> result = this.endpoint.listNames();
57-
assertThat(result).containsOnlyKeys("names");
58-
assertThat(result.get("names")).containsOnlyOnce("com.example.foo",
54+
MetricsEndpoint.ListNamesResponse result = this.endpoint.listNames();
55+
assertThat(result.getNames()).containsOnlyOnce("com.example.foo",
5956
"com.example.bar");
6057
}
6158

59+
@Test
60+
public void listNamesRecursesOverCompositeRegistries() {
61+
CompositeMeterRegistry composite = new CompositeMeterRegistry();
62+
SimpleMeterRegistry reg1 = new SimpleMeterRegistry();
63+
SimpleMeterRegistry reg2 = new SimpleMeterRegistry();
64+
composite.add(reg1);
65+
composite.add(reg2);
66+
67+
reg1.counter("counter1").increment();
68+
reg2.counter("counter2").increment();
69+
70+
MetricsEndpoint endpoint = new MetricsEndpoint(composite);
71+
assertThat(endpoint.listNames().getNames()).containsOnly("counter1", "counter2");
72+
}
73+
6274
@Test
6375
public void metricValuesAreTheSumOfAllTimeSeriesMatchingTags() {
6476
this.registry.counter("cache", "result", "hit", "host", "1").increment(2);
6577
this.registry.counter("cache", "result", "miss", "host", "1").increment(2);
6678
this.registry.counter("cache", "result", "hit", "host", "2").increment(2);
67-
MetricsEndpoint.Response response = this.endpoint.metric("cache",
79+
MetricsEndpoint.MetricResponse response = this.endpoint.metric("cache",
6880
Collections.emptyList());
6981
assertThat(response.getName()).isEqualTo("cache");
7082
assertThat(availableTagKeys(response)).containsExactly("result", "host");
@@ -77,29 +89,45 @@ public void metricValuesAreTheSumOfAllTimeSeriesMatchingTags() {
7789
@Test
7890
public void metricWithSpaceInTagValue() {
7991
this.registry.counter("counter", "key", "a space").increment(2);
80-
MetricsEndpoint.Response response = this.endpoint.metric("counter",
92+
MetricsEndpoint.MetricResponse response = this.endpoint.metric("counter",
8193
Collections.singletonList("key:a space"));
8294
assertThat(response.getName()).isEqualTo("counter");
8395
assertThat(availableTagKeys(response)).isEmpty();
8496
assertThat(getCount(response)).hasValue(2.0);
8597
}
8698

99+
@Test
100+
public void metricPresentInOneRegistryOfACompositeAndNotAnother() {
101+
CompositeMeterRegistry composite = new CompositeMeterRegistry();
102+
SimpleMeterRegistry reg1 = new SimpleMeterRegistry();
103+
SimpleMeterRegistry reg2 = new SimpleMeterRegistry();
104+
composite.add(reg1);
105+
composite.add(reg2);
106+
107+
reg1.counter("counter1").increment();
108+
reg2.counter("counter2").increment();
109+
110+
MetricsEndpoint endpoint = new MetricsEndpoint(composite);
111+
assertThat(endpoint.metric("counter1", Collections.emptyList())).isNotNull();
112+
assertThat(endpoint.metric("counter2", Collections.emptyList())).isNotNull();
113+
}
114+
87115
@Test
88116
public void nonExistentMetric() {
89-
MetricsEndpoint.Response response = this.endpoint.metric("does.not.exist",
117+
MetricsEndpoint.MetricResponse response = this.endpoint.metric("does.not.exist",
90118
Collections.emptyList());
91119
assertThat(response).isNull();
92120
}
93121

94-
private Optional<Double> getCount(MetricsEndpoint.Response response) {
122+
private Optional<Double> getCount(MetricsEndpoint.MetricResponse response) {
95123
return response.getMeasurements().stream()
96124
.filter((ms) -> ms.getStatistic().equals(Statistic.Count)).findAny()
97-
.map(MetricsEndpoint.Response.Sample::getValue);
125+
.map(MetricsEndpoint.MetricResponse.Sample::getValue);
98126
}
99127

100-
private Stream<String> availableTagKeys(MetricsEndpoint.Response response) {
128+
private Stream<String> availableTagKeys(MetricsEndpoint.MetricResponse response) {
101129
return response.getAvailableTags().stream()
102-
.map(MetricsEndpoint.Response.AvailableTag::getTag);
130+
.map(MetricsEndpoint.MetricResponse.AvailableTag::getTag);
103131
}
104132

105133
}

0 commit comments

Comments
 (0)