diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricsEndpoint.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricsEndpoint.java index 5382801948..ce13bcf9ae 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricsEndpoint.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/MetricsEndpoint.java @@ -17,17 +17,16 @@ 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; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.BiFunction; import java.util.stream.Collectors; -import java.util.stream.Stream; -import io.micrometer.core.instrument.Measurement; import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Statistic; @@ -44,6 +43,7 @@ import org.springframework.util.Assert; * An {@link Endpoint} for exposing the metrics held by a {@link MeterRegistry}. * * @author Jon Schneider + * @author Phillip Webb * @since 2.0.0 */ @Endpoint(id = "metrics") @@ -57,24 +57,22 @@ public class MetricsEndpoint { @ReadOperation public ListNamesResponse listNames() { - return new ListNamesResponse(recurseListNames(this.registry)); + Set names = new LinkedHashSet<>(); + collectNames(names, this.registry); + return new ListNamesResponse(names); } - private Set recurseListNames(MeterRegistry registry) { - Set names = new HashSet<>(); + private void collectNames(Set names, MeterRegistry registry) { if (registry instanceof CompositeMeterRegistry) { - for (MeterRegistry compositeMember : ((CompositeMeterRegistry) registry) - .getRegistries()) { - names.addAll(recurseListNames(compositeMember)); - } + ((CompositeMeterRegistry) registry).getRegistries() + .forEach((member) -> collectNames(names, member)); } else { - registry.getMeters().stream().map(this::getMeterIdName).forEach(names::add); + registry.getMeters().stream().map(this::getName).forEach(names::add); } - return names; } - private String getMeterIdName(Meter meter) { + private String getName(Meter meter) { return meter.getId().getName(); } @@ -84,52 +82,17 @@ public class MetricsEndpoint { Assert.isTrue(tag == null || tag.stream().allMatch((t) -> t.contains(":")), "Each tag parameter must be in the form key:value"); List tags = parseTags(tag); - Collection meters = recurseFindMeter(this.registry, requiredMetricName, tags); + List meters = new ArrayList<>(); + collectMeters(meters, this.registry, requiredMetricName, tags); if (meters.isEmpty()) { return null; } - - Map samples = new HashMap<>(); - Map> availableTags = new HashMap<>(); - - for (Meter meter : meters) { - for (Measurement ms : meter.measure()) { - samples.merge(ms.getStatistic(), ms.getValue(), Double::sum); - } - for (Tag availableTag : meter.getId().getTags()) { - availableTags.merge(availableTag.getKey(), - Collections.singletonList(availableTag.getValue()), - (t1, t2) -> Stream.concat(t1.stream(), t2.stream()) - .collect(Collectors.toList())); - } - } - + Map samples = getSamples(meters); + Map> availableTags = getAvailableTags(meters); tags.forEach((t) -> availableTags.remove(t.getKey())); - return new MetricResponse(requiredMetricName, - samples.entrySet().stream() - .map((sample) -> new MetricResponse.Sample(sample.getKey(), - sample.getValue())) - .collect(Collectors.toList()), - availableTags.entrySet().stream() - .map((tagValues) -> new MetricResponse.AvailableTag( - tagValues.getKey(), tagValues.getValue())) - .collect(Collectors.toList())); - } - - private Collection recurseFindMeter(MeterRegistry registry, String name, - Iterable tags) { - Collection 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; + asList(samples, MetricResponse.Sample::new), + asList(availableTags, MetricResponse.AvailableTag::new)); } private List parseTags(List tags) { @@ -139,6 +102,55 @@ public class MetricsEndpoint { }).collect(Collectors.toList()); } + private void collectMeters(List meters, MeterRegistry registry, String name, + Iterable tags) { + if (registry instanceof CompositeMeterRegistry) { + ((CompositeMeterRegistry) registry).getRegistries() + .forEach((member) -> collectMeters(meters, member, name, tags)); + } + else { + meters.addAll(registry.find(name).tags(tags).meters()); + } + } + + private Map getSamples(List meters) { + Map samples = new LinkedHashMap<>(); + meters.forEach((meter) -> mergeMeasurements(samples, meter)); + return samples; + } + + private void mergeMeasurements(Map samples, Meter meter) { + meter.measure().forEach((measurement) -> samples.merge(measurement.getStatistic(), + measurement.getValue(), Double::sum)); + } + + private Map> getAvailableTags(List meters) { + Map> availableTags = new HashMap<>(); + meters.forEach((meter) -> mergeAvailableTags(availableTags, meter)); + return availableTags; + } + + private void mergeAvailableTags(Map> availableTags, + Meter meter) { + meter.getId().getTags().forEach((tag) -> { + List value = Collections.singletonList(tag.getValue()); + availableTags.merge(tag.getKey(), value, this::merge); + }); + } + + private List merge(List list1, List list2) { + List result = new ArrayList<>(list1.size() + list2.size()); + result.addAll(list1); + result.addAll(list2); + return result; + } + + private List asList(Map map, BiFunction mapper) { + return map.entrySet().stream() + .map((entry) -> mapper.apply(entry.getKey(), entry.getValue())) + .collect(Collectors.toCollection(ArrayList::new)); + } + /** * Response payload for a metric name listing. */ @@ -235,6 +247,8 @@ public class MetricsEndpoint { return "MeasurementSample{" + "statistic=" + this.statistic + ", value=" + this.value + '}'; } + } + } } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java index 496dbb0fef..8b36d0cf11 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/MetricsEndpointTests.java @@ -63,10 +63,8 @@ public class MetricsEndpointTests { 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"); } @@ -103,10 +101,8 @@ public class MetricsEndpointTests { 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();