Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.boot.actuate.metrics;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
Expand Down Expand Up @@ -81,8 +82,8 @@ private String getName(Meter meter) {
public MetricResponse metric(@Selector String requiredMetricName,
@Nullable List<String> tag) {
List<Tag> tags = parseTags(tag);
List<Meter> meters = new ArrayList<>();
collectMeters(meters, this.registry, requiredMetricName, tags);
List<Meter> meters = findFirstMatchingMeters(this.registry, requiredMetricName,
tags);
if (meters.isEmpty()) {
return null;
}
Expand Down Expand Up @@ -112,15 +113,22 @@ private Tag parseTag(String tag) {
return Tag.of(parts[0], parts[1]);
}

private void collectMeters(List<Meter> meters, MeterRegistry registry, String name,
private List<Meter> findFirstMatchingMeters(MeterRegistry registry, String name,
Iterable<Tag> tags) {
if (registry instanceof CompositeMeterRegistry) {
((CompositeMeterRegistry) registry).getRegistries()
.forEach((member) -> collectMeters(meters, member, name, tags));
return ((CompositeMeterRegistry) registry).getRegistries().stream()
.map((r) -> findFirstMatchingMeters(r, name, tags))
.filter((match) -> !match.isEmpty()).findFirst()
.orElse(Collections.emptyList());

}
else {
meters.addAll(registry.find(name).tags(tags).meters());
Collection<Meter> metersFound = registry.find(name).tags(tags).meters();
if (!metersFound.isEmpty()) {
return new ArrayList<>(metersFound);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to wrap this. The list returned is already an immutable copy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jkschneider , the reason I am wrapping it into List is to avoid returning Collection from this method.

Collection<Meter> metersFound = registry.find(name).tags(tags).meters();

If we return Collection, the MetricEndpoint methods should be refactored so that Collection could be pass around instead of List interface OR the return type should be downcasted to List (which is dangerous as we dont have control on MeterRegistry#find)

Changing MetricEndpoint methods to pass Collection is minor refactoring, with following highlight.

will be change to

meters.iterator().next().getId();

Please confirm should I go ahead and do this minor change?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The minor refactoring seems preferable to me to generating extra garbage in this case, as minor as that will be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @jkschneider, pls review.

}
}
return Collections.emptyList();
}

private Map<Statistic, Double> getSamples(List<Meter> meters) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,55 @@ public void metricValuesAreTheSumOfAllTimeSeriesMatchingTags() {
assertThat(getCount(response)).hasValue(4.0);
}

@Test
public void findFirstMatchingMetersFromNestedRegistries() {
CompositeMeterRegistry composite = new CompositeMeterRegistry();
SimpleMeterRegistry reg1 = new SimpleMeterRegistry();
CompositeMeterRegistry reg2 = new CompositeMeterRegistry();
SimpleMeterRegistry reg3 = new SimpleMeterRegistry();

// 1st level nesting
composite.add(reg1);

// 2st level nesting
reg2.add(reg3);
composite.add(reg2);

// 2nd level registry has metrics
reg3.counter("cache", "result", "hit", "host", "1").increment(2);
reg3.counter("cache", "result", "miss", "host", "1").increment(2);
reg3.counter("cache", "result", "hit", "host", "2").increment(2);

MetricsEndpoint endpoint = new MetricsEndpoint(composite);

MetricsEndpoint.MetricResponse response = endpoint.metric("cache",
Collections.emptyList());
assertThat(response.getName()).isEqualTo("cache");
assertThat(availableTagKeys(response)).containsExactly("result", "host");
assertThat(getCount(response)).hasValue(6.0);

response = endpoint.metric("cache", Collections.singletonList("result:hit"));
assertThat(availableTagKeys(response)).containsExactly("host");
assertThat(getCount(response)).hasValue(4.0);
}

@Test
public void matchingMeterNotFoundInNestedRegistries() {
CompositeMeterRegistry composite = new CompositeMeterRegistry();
CompositeMeterRegistry reg2 = new CompositeMeterRegistry();
SimpleMeterRegistry reg3 = new SimpleMeterRegistry();

// nested registries
reg2.add(reg3);
composite.add(reg2);

MetricsEndpoint endpoint = new MetricsEndpoint(composite);

MetricsEndpoint.MetricResponse response = endpoint.metric("invalid.metric.name",
Collections.emptyList());
assertThat(response).isNull();
}

@Test
public void metricTagValuesAreDeduplicated() {
this.registry.counter("cache", "host", "1", "region", "east", "result", "hit");
Expand Down