Merge pull request #10519 from Jon Schneider
* gh-10519: Improve new metrics endpoint Add auto-configuration for exporting metrics to StatsD Make the Graphite export protocol configurable Start building against Micrometer snapshots for 1.0.0-rc.2 Closes gh-10519
This commit is contained in:
commit
7d5f3a341f
|
|
@ -122,6 +122,11 @@
|
|||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-statsd</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.searchbox</groupId>
|
||||
<artifactId>jest</artifactId>
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@
|
|||
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics;
|
||||
|
||||
import io.micrometer.core.instrument.binder.JvmMemoryMetrics;
|
||||
import io.micrometer.core.instrument.binder.LogbackMetrics;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import io.micrometer.core.instrument.binder.UptimeMetrics;
|
||||
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
|
||||
import io.micrometer.core.instrument.binder.logging.LogbackMetrics;
|
||||
import io.micrometer.core.instrument.binder.system.UptimeMetrics;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import org.springframework.boot.actuate.autoconfigure.metrics.export.influx.Infl
|
|||
import org.springframework.boot.actuate.autoconfigure.metrics.export.jmx.JmxExportConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusExportConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleExportConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.statsd.StatsdExportConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.reactive.server.WebFluxMetricsConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.web.client.RestTemplateMetricsConfiguration;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsConfiguration;
|
||||
|
|
@ -67,7 +68,8 @@ import org.springframework.integration.support.management.IntegrationManagementC
|
|||
AtlasExportConfiguration.class, DatadogExportConfiguration.class,
|
||||
GangliaExportConfiguration.class, GraphiteExportConfiguration.class,
|
||||
InfluxExportConfiguration.class, JmxExportConfiguration.class,
|
||||
PrometheusExportConfiguration.class, SimpleExportConfiguration.class })
|
||||
PrometheusExportConfiguration.class, SimpleExportConfiguration.class,
|
||||
StatsdExportConfiguration.class })
|
||||
public class MetricsAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.graphite;
|
|||
import java.time.Duration;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.micrometer.graphite.GraphiteProtocol;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
|
|
@ -29,6 +31,7 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
|||
*/
|
||||
@ConfigurationProperties(prefix = "spring.metrics.graphite")
|
||||
public class GraphiteProperties {
|
||||
|
||||
/**
|
||||
* Enable publishing to the backend.
|
||||
*/
|
||||
|
|
@ -59,6 +62,11 @@ public class GraphiteProperties {
|
|||
*/
|
||||
private Integer port;
|
||||
|
||||
/**
|
||||
* Protocol to use while shipping data to Graphite.
|
||||
*/
|
||||
private GraphiteProtocol protocol = GraphiteProtocol.Pickled;
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
|
@ -106,4 +114,13 @@ public class GraphiteProperties {
|
|||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public GraphiteProtocol getProtocol() {
|
||||
return this.protocol;
|
||||
}
|
||||
|
||||
public void setProtocol(GraphiteProtocol protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.time.Duration;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.micrometer.graphite.GraphiteConfig;
|
||||
import io.micrometer.graphite.GraphiteProtocol;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesConfigAdapter;
|
||||
|
||||
|
|
@ -74,4 +75,9 @@ class GraphitePropertiesConfigAdapter
|
|||
return get(GraphiteProperties::getPort, GraphiteConfig::port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphiteProtocol protocol() {
|
||||
return get(GraphiteProperties::getProtocol, GraphiteConfig::protocol);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics.export.statsd;
|
||||
|
||||
import io.micrometer.core.instrument.Clock;
|
||||
import io.micrometer.statsd.StatsdConfig;
|
||||
import io.micrometer.statsd.StatsdMeterRegistry;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.MetricsExporter;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.StringToDurationConverter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
/**
|
||||
* Configuration for exporting metrics to StatsD.
|
||||
*
|
||||
* @author Jon Schneider
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnClass(StatsdMeterRegistry.class)
|
||||
@Import(StringToDurationConverter.class)
|
||||
@EnableConfigurationProperties(StatsdProperties.class)
|
||||
public class StatsdExportConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(StatsdConfig.class)
|
||||
public StatsdConfig statsdConfig(StatsdProperties statsdProperties) {
|
||||
return new StatsdPropertiesConfigAdapter(statsdProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(value = "spring.metrics.statsd.enabled", matchIfMissing = true)
|
||||
public MetricsExporter statsdExporter(StatsdConfig statsdConfig, Clock clock) {
|
||||
return () -> new StatsdMeterRegistry(statsdConfig, clock);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public Clock micrometerClock() {
|
||||
return Clock.SYSTEM;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics.export.statsd;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import io.micrometer.statsd.StatsdFlavor;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
/**
|
||||
* {@link ConfigurationProperties} for configuring StatsD metrics export.
|
||||
*
|
||||
* @author Jon Schneider
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@ConfigurationProperties(prefix = "spring.metrics.statsd")
|
||||
public class StatsdProperties {
|
||||
|
||||
/**
|
||||
* Enable publishing to the backend.
|
||||
*/
|
||||
private Boolean enabled = true;
|
||||
|
||||
/**
|
||||
* Variant of the StatsD line protocol to use.
|
||||
*/
|
||||
private StatsdFlavor flavor = StatsdFlavor.Datadog;
|
||||
|
||||
/**
|
||||
* Host name of the StatsD agent.
|
||||
*/
|
||||
private String host = "localhost";
|
||||
|
||||
/**
|
||||
* UDP port of the StatsD agent.
|
||||
*/
|
||||
private Integer port = 8125;
|
||||
|
||||
/**
|
||||
* Total length of a single payload should be kept within your network's MTU.
|
||||
*/
|
||||
private Integer maxPacketLength = 1400;
|
||||
|
||||
/**
|
||||
* Determines how often gauges will be polled. When a gauge is polled, its value is
|
||||
* recalculated. If the value has changed, it is sent to the StatsD server.
|
||||
*/
|
||||
private Duration pollingFrequency = Duration.ofSeconds(10);
|
||||
|
||||
/**
|
||||
* Governs the maximum size of the queue of items waiting to be sent to a StatsD agent
|
||||
* over UDP.
|
||||
*/
|
||||
private Integer queueSize = Integer.MAX_VALUE;
|
||||
|
||||
/**
|
||||
* Used to create a bucket filter clamping the bucket domain of timer percentiles
|
||||
* histograms to some max value. This is used to limit the number of buckets shipped
|
||||
* to StatsD to save on storage.
|
||||
*/
|
||||
private Duration timerPercentilesMax = Duration.ofMinutes(2);
|
||||
|
||||
/**
|
||||
* Used to create a bucket filter clamping the bucket domain of timer percentiles
|
||||
* histograms to some min value. This is used to limit the number of buckets shipped
|
||||
* to StatsD to save on storage.
|
||||
*/
|
||||
private Duration timerPercentilesMin = Duration.ofMillis(10);
|
||||
|
||||
public Duration getTimerPercentilesMax() {
|
||||
return this.timerPercentilesMax;
|
||||
}
|
||||
|
||||
public void setTimerPercentilesMax(Duration timerPercentilesMax) {
|
||||
this.timerPercentilesMax = timerPercentilesMax;
|
||||
}
|
||||
|
||||
public Duration getTimerPercentilesMin() {
|
||||
return this.timerPercentilesMin;
|
||||
}
|
||||
|
||||
public void setTimerPercentilesMin(Duration timerPercentilesMin) {
|
||||
this.timerPercentilesMin = timerPercentilesMin;
|
||||
}
|
||||
|
||||
public Boolean getEnabled() {
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(Boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public StatsdFlavor getFlavor() {
|
||||
return this.flavor;
|
||||
}
|
||||
|
||||
public void setFlavor(StatsdFlavor flavor) {
|
||||
this.flavor = flavor;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return this.host;
|
||||
}
|
||||
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
public Integer getPort() {
|
||||
return this.port;
|
||||
}
|
||||
|
||||
public void setPort(Integer port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public Integer getMaxPacketLength() {
|
||||
return this.maxPacketLength;
|
||||
}
|
||||
|
||||
public void setMaxPacketLength(Integer maxPacketLength) {
|
||||
this.maxPacketLength = maxPacketLength;
|
||||
}
|
||||
|
||||
public Duration getPollingFrequency() {
|
||||
return this.pollingFrequency;
|
||||
}
|
||||
|
||||
public void setPollingFrequency(Duration pollingFrequency) {
|
||||
this.pollingFrequency = pollingFrequency;
|
||||
}
|
||||
|
||||
public Integer getQueueSize() {
|
||||
return this.queueSize;
|
||||
}
|
||||
|
||||
public void setQueueSize(Integer queueSize) {
|
||||
this.queueSize = queueSize;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics.export.statsd;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import io.micrometer.statsd.StatsdConfig;
|
||||
import io.micrometer.statsd.StatsdFlavor;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.export.PropertiesConfigAdapter;
|
||||
|
||||
/**
|
||||
* Adapter to convert {@link StatsdProperties} to a {@link StatsdConfig}.
|
||||
*
|
||||
* @author Jon Schneider
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class StatsdPropertiesConfigAdapter extends
|
||||
PropertiesConfigAdapter<StatsdProperties, StatsdConfig> implements StatsdConfig {
|
||||
|
||||
private static final StatsdConfig DEFAULTS = (key) -> null;
|
||||
|
||||
public StatsdPropertiesConfigAdapter(StatsdProperties properties) {
|
||||
super(properties, DEFAULTS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String get(String s) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StatsdFlavor flavor() {
|
||||
return get(StatsdProperties::getFlavor, StatsdConfig::flavor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enabled() {
|
||||
return get(StatsdProperties::getEnabled, StatsdConfig::enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String host() {
|
||||
return get(StatsdProperties::getHost, StatsdConfig::host);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int port() {
|
||||
return get(StatsdProperties::getPort, StatsdConfig::port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int maxPacketLength() {
|
||||
return get(StatsdProperties::getMaxPacketLength, StatsdConfig::maxPacketLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration pollingFrequency() {
|
||||
return get(StatsdProperties::getPollingFrequency, StatsdConfig::pollingFrequency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int queueSize() {
|
||||
return get(StatsdProperties::getQueueSize, StatsdConfig::queueSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2012-2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Support for exporting actuator metrics to StatsD.
|
||||
*/
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics.export.statsd;
|
||||
|
|
@ -22,9 +22,9 @@ import java.util.Set;
|
|||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Statistic;
|
||||
import io.micrometer.core.instrument.binder.JvmMemoryMetrics;
|
||||
import io.micrometer.core.instrument.binder.LogbackMetrics;
|
||||
import io.micrometer.core.instrument.binder.MeterBinder;
|
||||
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
|
||||
import io.micrometer.core.instrument.binder.logging.LogbackMetrics;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ public class SimpleExportConfigurationTests {
|
|||
"spring.metrics.graphite.enabled=false",
|
||||
"spring.metrics.influx.enabled=false",
|
||||
"spring.metrics.jmx.enabled=false",
|
||||
"spring.metrics.prometheus.enabled=false")
|
||||
"spring.metrics.prometheus.enabled=false",
|
||||
"spring.metrics.statsd.enabled=false")
|
||||
.withConfiguration(AutoConfigurations.of(MetricsAutoConfiguration.class))
|
||||
.run((context) -> {
|
||||
CompositeMeterRegistry meterRegistry = context
|
||||
|
|
|
|||
|
|
@ -18,22 +18,23 @@ package org.springframework.boot.actuate.metrics;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import io.micrometer.core.instrument.Measurement;
|
||||
import io.micrometer.core.instrument.Meter;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.NamingConvention;
|
||||
import io.micrometer.core.instrument.Statistic;
|
||||
import io.micrometer.core.instrument.util.HierarchicalNameMapper;
|
||||
import io.micrometer.core.instrument.Tag;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Selector;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link Endpoint} for exposing the metrics held by a {@link MeterRegistry}.
|
||||
|
|
@ -61,57 +62,133 @@ public class MetricsEndpoint {
|
|||
}
|
||||
|
||||
@ReadOperation
|
||||
public Map<String, Collection<MeasurementSample>> metric(
|
||||
@Selector String requiredMetricName) {
|
||||
return this.registry.find(requiredMetricName).meters().stream()
|
||||
.collect(Collectors.toMap(this::getHierarchicalName, this::getSamples));
|
||||
public Response metric(@Selector String requiredMetricName,
|
||||
@Nullable List<String> tag) {
|
||||
Assert.isTrue(tag == null || tag.stream().allMatch((t) -> t.contains(":")),
|
||||
"Each tag parameter must be in the form key:value");
|
||||
List<Tag> tags = parseTags(tag);
|
||||
Collection<Meter> meters = this.registry.find(requiredMetricName).tags(tags)
|
||||
.meters();
|
||||
if (meters.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<Statistic, Double> samples = new HashMap<>();
|
||||
Map<String, List<String>> 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()));
|
||||
}
|
||||
}
|
||||
|
||||
tags.forEach((t) -> availableTags.remove(t.getKey()));
|
||||
|
||||
return new Response(requiredMetricName,
|
||||
samples.entrySet().stream()
|
||||
.map((sample) -> new Response.Sample(sample.getKey(),
|
||||
sample.getValue()))
|
||||
.collect(
|
||||
Collectors.toList()),
|
||||
availableTags.entrySet().stream()
|
||||
.map((tagValues) -> new Response.AvailableTag(tagValues.getKey(),
|
||||
tagValues.getValue()))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
private List<MeasurementSample> getSamples(Meter meter) {
|
||||
return stream(meter.measure()).map(this::getSample).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private MeasurementSample getSample(Measurement measurement) {
|
||||
return new MeasurementSample(measurement.getStatistic(), measurement.getValue());
|
||||
}
|
||||
|
||||
private String getHierarchicalName(Meter meter) {
|
||||
return HierarchicalNameMapper.DEFAULT.toHierarchicalName(meter.getId(),
|
||||
NamingConvention.camelCase);
|
||||
}
|
||||
|
||||
private <T> Stream<T> stream(Iterable<T> measure) {
|
||||
return StreamSupport.stream(measure.spliterator(), false);
|
||||
private List<Tag> parseTags(List<String> tags) {
|
||||
return tags == null ? Collections.emptyList() : tags.stream().map((t) -> {
|
||||
String[] tagParts = t.split(":", 2);
|
||||
return Tag.of(tagParts[0], tagParts[1]);
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* A measurement sample combining a {@link Statistic statistic} and a value.
|
||||
* Response payload.
|
||||
*/
|
||||
static class MeasurementSample {
|
||||
static class Response {
|
||||
|
||||
private final Statistic statistic;
|
||||
private final String name;
|
||||
|
||||
private final Double value;
|
||||
private final List<Sample> measurements;
|
||||
|
||||
MeasurementSample(Statistic statistic, Double value) {
|
||||
this.statistic = statistic;
|
||||
this.value = value;
|
||||
private final List<AvailableTag> availableTags;
|
||||
|
||||
Response(String name, List<Sample> measurements,
|
||||
List<AvailableTag> availableTags) {
|
||||
this.name = name;
|
||||
this.measurements = measurements;
|
||||
this.availableTags = availableTags;
|
||||
}
|
||||
|
||||
public Statistic getStatistic() {
|
||||
return this.statistic;
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public Double getValue() {
|
||||
return this.value;
|
||||
public List<Sample> getMeasurements() {
|
||||
return this.measurements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MeasurementSample{" + "statistic=" + this.statistic + ", value="
|
||||
+ this.value + '}';
|
||||
public List<AvailableTag> getAvailableTags() {
|
||||
return this.availableTags;
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of tags for further dimensional drilldown and their potential values.
|
||||
*/
|
||||
static class AvailableTag {
|
||||
|
||||
private final String tag;
|
||||
|
||||
private final List<String> values;
|
||||
|
||||
AvailableTag(String tag, List<String> values) {
|
||||
this.tag = tag;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return this.tag;
|
||||
}
|
||||
|
||||
public List<String> getValues() {
|
||||
return this.values;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A measurement sample combining a {@link Statistic statistic} and a value.
|
||||
*/
|
||||
static class Sample {
|
||||
|
||||
private final Statistic statistic;
|
||||
|
||||
private final Double value;
|
||||
|
||||
Sample(Statistic statistic, Double value) {
|
||||
this.statistic = statistic;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Statistic getStatistic() {
|
||||
return this.statistic;
|
||||
}
|
||||
|
||||
public Double getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MeasurementSample{" + "statistic=" + this.statistic + ", value="
|
||||
+ this.value + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,34 +16,33 @@
|
|||
|
||||
package org.springframework.boot.actuate.metrics;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import io.micrometer.core.instrument.Meter;
|
||||
import io.micrometer.core.instrument.Meter.Id;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.simple.SimpleCounter;
|
||||
import io.micrometer.core.instrument.Statistic;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link MetricsEndpoint}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Jon Schneider
|
||||
*/
|
||||
public class MetricsEndpointTests {
|
||||
|
||||
private final MeterRegistry registry = mock(MeterRegistry.class);
|
||||
private final MeterRegistry registry = new SimpleMeterRegistry();
|
||||
|
||||
private final MetricsEndpoint endpoint = new MetricsEndpoint(this.registry);
|
||||
|
||||
@Test
|
||||
public void listNamesHandlesEmptyListOfMeters() {
|
||||
given(this.registry.getMeters()).willReturn(Arrays.asList());
|
||||
Map<String, List<String>> result = this.endpoint.listNames();
|
||||
assertThat(result).containsOnlyKeys("names");
|
||||
assertThat(result.get("names")).isEmpty();
|
||||
|
|
@ -51,23 +50,56 @@ public class MetricsEndpointTests {
|
|||
|
||||
@Test
|
||||
public void listNamesProducesListOfUniqueMeterNames() {
|
||||
List<Meter> meters = Arrays.asList(createCounter("com.example.foo"),
|
||||
createCounter("com.example.bar"), createCounter("com.example.foo"));
|
||||
given(this.registry.getMeters()).willReturn(meters);
|
||||
this.registry.counter("com.example.foo");
|
||||
this.registry.counter("com.example.bar");
|
||||
this.registry.counter("com.example.foo");
|
||||
Map<String, List<String>> result = this.endpoint.listNames();
|
||||
assertThat(result).containsOnlyKeys("names");
|
||||
assertThat(result.get("names")).containsOnlyOnce("com.example.foo",
|
||||
"com.example.bar");
|
||||
}
|
||||
|
||||
private Meter createCounter(String name) {
|
||||
return new SimpleCounter(createMeterId(name));
|
||||
@Test
|
||||
public void metricValuesAreTheSumOfAllTimeSeriesMatchingTags() {
|
||||
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", "hit", "host", "2").increment(2);
|
||||
MetricsEndpoint.Response response = this.endpoint.metric("cache",
|
||||
Collections.emptyList());
|
||||
assertThat(response.getName()).isEqualTo("cache");
|
||||
assertThat(availableTagKeys(response)).containsExactly("result", "host");
|
||||
assertThat(getCount(response)).hasValue(6.0);
|
||||
response = this.endpoint.metric("cache", Collections.singletonList("result:hit"));
|
||||
assertThat(availableTagKeys(response)).containsExactly("host");
|
||||
assertThat(getCount(response)).hasValue(4.0);
|
||||
}
|
||||
|
||||
private Id createMeterId(String name) {
|
||||
Id id = mock(Id.class);
|
||||
given(id.getName()).willReturn(name);
|
||||
return id;
|
||||
@Test
|
||||
public void metricWithSpaceInTagValue() {
|
||||
this.registry.counter("counter", "key", "a space").increment(2);
|
||||
MetricsEndpoint.Response response = this.endpoint.metric("counter",
|
||||
Collections.singletonList("key:a space"));
|
||||
assertThat(response.getName()).isEqualTo("counter");
|
||||
assertThat(availableTagKeys(response)).isEmpty();
|
||||
assertThat(getCount(response)).hasValue(2.0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonExistentMetric() {
|
||||
MetricsEndpoint.Response response = this.endpoint.metric("does.not.exist",
|
||||
Collections.emptyList());
|
||||
assertThat(response).isNull();
|
||||
}
|
||||
|
||||
private Optional<Double> getCount(MetricsEndpoint.Response response) {
|
||||
return response.getMeasurements().stream()
|
||||
.filter((ms) -> ms.getStatistic().equals(Statistic.Count)).findAny()
|
||||
.map(MetricsEndpoint.Response.Sample::getValue);
|
||||
}
|
||||
|
||||
private Stream<String> availableTagKeys(MetricsEndpoint.Response response) {
|
||||
return response.getAvailableTags().stream()
|
||||
.map(MetricsEndpoint.Response.AvailableTag::getTag);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import java.util.Map;
|
|||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.binder.JvmMemoryMetrics;
|
||||
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
|
@ -61,9 +61,15 @@ public class MetricsEndpointWebIntegrationTests {
|
|||
public void selectByName() throws IOException {
|
||||
MetricsEndpointWebIntegrationTests.client.get()
|
||||
.uri("/application/metrics/jvm.memory.used").exchange().expectStatus()
|
||||
.isOk().expectBody()
|
||||
.jsonPath("['jvmMemoryUsed.area.nonheap.id.Compressed_Class_Space']")
|
||||
.exists().jsonPath("['jvmMemoryUsed.area.heap.id.PS_Old_Gen']");
|
||||
.isOk().expectBody().jsonPath("$.name").isEqualTo("jvm.memory.used");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void selectByTag() {
|
||||
MetricsEndpointWebIntegrationTests.client.get()
|
||||
.uri("/application/metrics/jvm.memory.used?tag=id:PS%20Old%20Gen")
|
||||
.exchange().expectStatus().isOk().expectBody().jsonPath("$.name")
|
||||
.isEqualTo("jvm.memory.used");
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@
|
|||
<logback.version>1.2.3</logback.version>
|
||||
<lombok.version>1.16.18</lombok.version>
|
||||
<mariadb.version>2.1.2</mariadb.version>
|
||||
<micrometer.version>1.0.0-rc.1</micrometer.version>
|
||||
<micrometer.version>1.0.0-SNAPSHOT</micrometer.version>
|
||||
<mssql-jdbc.version>6.2.1.jre8</mssql-jdbc.version>
|
||||
<mockito.version>2.10.0</mockito.version>
|
||||
<mongo-driver-reactivestreams.version>1.6.0</mongo-driver-reactivestreams.version>
|
||||
|
|
@ -942,6 +942,11 @@
|
|||
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||
<version>${micrometer.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.micrometer</groupId>
|
||||
<artifactId>micrometer-registry-statsd</artifactId>
|
||||
<version>${micrometer.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-bom</artifactId>
|
||||
|
|
|
|||
Loading…
Reference in New Issue