Extract AutoTimer interface for metrics

Refactor `Autotime` from a properties object to an interface and
change the existing metric recording implementations. The `AutoTimer`
interface is a general purpose callback that can be applied to a
`Timer.Builder` to configure it. Autotime properties are now located
in `spring-boot-actuator-autoconfigure` and have become an
implementation of the interface.

Closes gh-17026
pull/17010/head
Phillip Webb 6 years ago
parent ad5e905bd7
commit 68a3fbd7a0

@ -14,18 +14,21 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.boot.actuate.metrics; package org.springframework.boot.actuate.autoconfigure.metrics;
import java.util.List; import io.micrometer.core.instrument.Timer.Builder;
import org.springframework.boot.actuate.metrics.AutoTimer;
/** /**
* Settings for requests that are automatically timed. * Nested configuration properties for items that are automatically timed.
* *
* @author Tadaya Tsuyukubo * @author Tadaya Tsuyukubo
* @author Stephane Nicoll * @author Stephane Nicoll
* @author Phillip Webb
* @since 2.2.0 * @since 2.2.0
*/ */
public final class Autotime { public final class AutoTimeProperties implements AutoTimer {
private boolean enabled = true; private boolean enabled = true;
@ -36,24 +39,10 @@ public final class Autotime {
/** /**
* Create an instance that automatically time requests with no percentiles. * Create an instance that automatically time requests with no percentiles.
*/ */
public Autotime() { public AutoTimeProperties() {
}
/**
* Create an instance with the specified settings.
* @param enabled whether requests should be automatically timed
* @param percentilesHistogram whether percentile histograms should be published
* @param percentiles computed non-aggregable percentiles to publish (can be
* {@code null})
*/
public Autotime(boolean enabled, boolean percentilesHistogram,
List<Double> percentiles) {
this.enabled = enabled;
this.percentilesHistogram = percentilesHistogram;
this.percentiles = (percentiles != null)
? percentiles.stream().mapToDouble(Double::doubleValue).toArray() : null;
} }
@Override
public boolean isEnabled() { public boolean isEnabled() {
return this.enabled; return this.enabled;
} }
@ -78,12 +67,10 @@ public final class Autotime {
this.percentiles = percentiles; this.percentiles = percentiles;
} }
/** @Override
* Create an instance that disable auto-timed requests. public void apply(Builder builder) {
* @return an instance that disable auto-timed requests builder.publishPercentileHistogram(this.percentilesHistogram)
*/ .publishPercentiles(this.percentiles);
public static Autotime disabled() {
return new Autotime(false, false, null);
} }
} }

@ -19,7 +19,6 @@ package org.springframework.boot.actuate.autoconfigure.metrics;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import org.springframework.boot.actuate.metrics.Autotime;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.boot.context.properties.NestedConfigurationProperty; import org.springframework.boot.context.properties.NestedConfigurationProperty;
@ -116,6 +115,7 @@ public class MetricsProperties {
* @return request metric name * @return request metric name
* @deprecated since 2.2.0 in favor of {@link ClientRequest#getMetricName()} * @deprecated since 2.2.0 in favor of {@link ClientRequest#getMetricName()}
*/ */
@Deprecated
@DeprecatedConfigurationProperty( @DeprecatedConfigurationProperty(
replacement = "management.metrics.web.client.request.metric-name") replacement = "management.metrics.web.client.request.metric-name")
public String getRequestsMetricName() { public String getRequestsMetricName() {
@ -128,6 +128,7 @@ public class MetricsProperties {
* @deprecated since 2.2.0 in favor of * @deprecated since 2.2.0 in favor of
* {@link ClientRequest#setMetricName(String)} * {@link ClientRequest#setMetricName(String)}
*/ */
@Deprecated
public void setRequestsMetricName(String requestsMetricName) { public void setRequestsMetricName(String requestsMetricName) {
this.request.setMetricName(requestsMetricName); this.request.setMetricName(requestsMetricName);
} }
@ -151,10 +152,10 @@ public class MetricsProperties {
* Auto-timed request settings. * Auto-timed request settings.
*/ */
@NestedConfigurationProperty @NestedConfigurationProperty
private final Autotime autoTime = new Autotime(); private final AutoTimeProperties autotime = new AutoTimeProperties();
public Autotime getAutotime() { public AutoTimeProperties getAutotime() {
return this.autoTime; return this.autotime;
} }
public String getMetricName() { public String getMetricName() {
@ -187,7 +188,7 @@ public class MetricsProperties {
/** /**
* Return whether server requests should be automatically timed. * Return whether server requests should be automatically timed.
* @return {@code true} if server request should be automatically timed * @return {@code true} if server request should be automatically timed
* @deprecated since 2.2.0 in favor of {@link Autotime#isEnabled()} * @deprecated since 2.2.0 in favor of {@link AutoTimeProperties#isEnabled()}
*/ */
@DeprecatedConfigurationProperty( @DeprecatedConfigurationProperty(
replacement = "management.metrics.web.server.request.autotime.enabled") replacement = "management.metrics.web.server.request.autotime.enabled")
@ -200,7 +201,7 @@ public class MetricsProperties {
* Set whether server requests should be automatically timed. * Set whether server requests should be automatically timed.
* @param autoTimeRequests whether server requests should be automatically * @param autoTimeRequests whether server requests should be automatically
* timed * timed
* @deprecated since 2.2.0 in favor of {@link Autotime#isEnabled()} * @deprecated since 2.2.0 in favor of {@link AutoTimeProperties#isEnabled()}
*/ */
@Deprecated @Deprecated
public void setAutoTimeRequests(boolean autoTimeRequests) { public void setAutoTimeRequests(boolean autoTimeRequests) {
@ -249,9 +250,9 @@ public class MetricsProperties {
* Auto-timed request settings. * Auto-timed request settings.
*/ */
@NestedConfigurationProperty @NestedConfigurationProperty
private final Autotime autotime = new Autotime(); private final AutoTimeProperties autotime = new AutoTimeProperties();
public Autotime getAutotime() { public AutoTimeProperties getAutotime() {
return this.autotime; return this.autotime;
} }

@ -0,0 +1,100 @@
/*
* Copyright 2012-2019 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
*
* https://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.metrics;
/**
* @author pwebb
*/
import java.util.function.Supplier;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.Timer.Builder;
/**
* Strategy that can be used to apply {@link Timer Timers} automatically instead of using
* {@link Timed @Timed}.
*
* @author Tadaya Tsuyukubo
* @author Stephane Nicoll
* @author Phillip Webb
* @since 2.2.0
*/
@FunctionalInterface
public interface AutoTimer {
/**
* An {@link AutoTimer} implementation that is enabled but applies no additional
* customizations.
*/
AutoTimer ENABLED = (builder) -> {
};
/**
* An {@link AutoTimer} implementation that is disabled and will not record metrics.
*/
AutoTimer DISABLED = new AutoTimer() {
@Override
public boolean isEnabled() {
return false;
}
@Override
public void apply(Builder builder) {
throw new IllegalStateException("AutoTimer is disabled");
}
};
/**
* Return if the auto-timer is enabled and metrics should be recorded.
* @return if the auto-timer is enabled
*/
default boolean isEnabled() {
return true;
}
/**
* Factory method to create a new {@link Builder Timer.Builder} with auto-timer
* settings {@link #apply(Builder) applied}.
* @param name the name of the timer
* @return a new builder instance with auto-settings applied
*/
default Timer.Builder builder(String name) {
return builder(() -> Timer.builder(name));
}
/**
* Factory method to create a new {@link Builder Timer.Builder} with auto-timer
* settings {@link #apply(Builder) applied}.
* @param supplier the builder supplier
* @return a new builder instance with auto-settings applied
*/
default Timer.Builder builder(Supplier<Timer.Builder> supplier) {
Timer.Builder builder = supplier.get();
apply(builder);
return builder;
}
/**
* Called to apply any auto-timer settings to the given {@link Builder Timer.Builder}.
* @param builder the builder to apply settings to
*/
void apply(Timer.Builder builder);
}

@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.Timer;
import org.springframework.boot.actuate.metrics.Autotime; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.core.NamedThreadLocal; import org.springframework.core.NamedThreadLocal;
import org.springframework.http.HttpRequest; import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestExecution;
@ -51,7 +51,7 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
private final String metricName; private final String metricName;
private final Autotime autotime; private final AutoTimer autoTimer;
/** /**
* Create a new {@code MetricsClientHttpRequestInterceptor}. * Create a new {@code MetricsClientHttpRequestInterceptor}.
@ -59,11 +59,12 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
* @param tagProvider provider for metrics tags * @param tagProvider provider for metrics tags
* @param metricName name of the metric to record * @param metricName name of the metric to record
* @deprecated since 2.2.0 in favor of * @deprecated since 2.2.0 in favor of
* {@link #MetricsClientHttpRequestInterceptor(MeterRegistry, RestTemplateExchangeTagsProvider, String, Autotime)} * {@link #MetricsClientHttpRequestInterceptor(MeterRegistry, RestTemplateExchangeTagsProvider, String, AutoTimer)}
*/ */
@Deprecated
MetricsClientHttpRequestInterceptor(MeterRegistry meterRegistry, MetricsClientHttpRequestInterceptor(MeterRegistry meterRegistry,
RestTemplateExchangeTagsProvider tagProvider, String metricName) { RestTemplateExchangeTagsProvider tagProvider, String metricName) {
this(meterRegistry, tagProvider, metricName, new Autotime()); this(meterRegistry, tagProvider, metricName, AutoTimer.ENABLED);
} }
/** /**
@ -71,21 +72,24 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
* @param meterRegistry the registry to which metrics are recorded * @param meterRegistry the registry to which metrics are recorded
* @param tagProvider provider for metrics tags * @param tagProvider provider for metrics tags
* @param metricName name of the metric to record * @param metricName name of the metric to record
* @param autotime auto timed request settings * @param autoTimer the auto-timers to apply or {@code null} to disable auto-timing
* @since 2.2.0 * @since 2.2.0
*/ */
MetricsClientHttpRequestInterceptor(MeterRegistry meterRegistry, MetricsClientHttpRequestInterceptor(MeterRegistry meterRegistry,
RestTemplateExchangeTagsProvider tagProvider, String metricName, RestTemplateExchangeTagsProvider tagProvider, String metricName,
Autotime autotime) { AutoTimer autoTimer) {
this.tagProvider = tagProvider; this.tagProvider = tagProvider;
this.meterRegistry = meterRegistry; this.meterRegistry = meterRegistry;
this.metricName = metricName; this.metricName = metricName;
this.autotime = (autotime != null) ? autotime : Autotime.disabled(); this.autoTimer = (autoTimer != null) ? autoTimer : AutoTimer.DISABLED;
} }
@Override @Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException { ClientHttpRequestExecution execution) throws IOException {
if (!this.autoTimer.isEnabled()) {
return execution.execute(request, body);
}
long startTime = System.nanoTime(); long startTime = System.nanoTime();
ClientHttpResponse response = null; ClientHttpResponse response = null;
try { try {
@ -93,10 +97,8 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
return response; return response;
} }
finally { finally {
if (this.autotime.isEnabled()) { getTimeBuilder(request, response).register(this.meterRegistry)
getTimeBuilder(request, response).register(this.meterRegistry) .record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
.record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
}
urlTemplate.remove(); urlTemplate.remove();
} }
} }
@ -121,9 +123,7 @@ class MetricsClientHttpRequestInterceptor implements ClientHttpRequestIntercepto
private Timer.Builder getTimeBuilder(HttpRequest request, private Timer.Builder getTimeBuilder(HttpRequest request,
ClientHttpResponse response) { ClientHttpResponse response) {
return Timer.builder(this.metricName) return this.autoTimer.builder(this.metricName)
.publishPercentiles(this.autotime.getPercentiles())
.publishPercentileHistogram(this.autotime.isPercentilesHistogram())
.tags(this.tagProvider.getTags(urlTemplate.get(), request, response)) .tags(this.tagProvider.getTags(urlTemplate.get(), request, response))
.description("Timer of RestTemplate operation"); .description("Timer of RestTemplate operation");
} }

@ -21,7 +21,7 @@ import java.util.List;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.actuate.metrics.Autotime; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.boot.web.client.RestTemplateCustomizer; import org.springframework.boot.web.client.RestTemplateCustomizer;
import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
@ -47,29 +47,30 @@ public class MetricsRestTemplateCustomizer implements RestTemplateCustomizer {
* @param tagProvider the tag provider * @param tagProvider the tag provider
* @param metricName the name of the recorded metric * @param metricName the name of the recorded metric
* @deprecated since 2.2.0 in favor of * @deprecated since 2.2.0 in favor of
* {@link #MetricsRestTemplateCustomizer(MeterRegistry, RestTemplateExchangeTagsProvider, String, Autotime)} * {@link #MetricsRestTemplateCustomizer(MeterRegistry, RestTemplateExchangeTagsProvider, String, AutoTimer)}
*/ */
@Deprecated
public MetricsRestTemplateCustomizer(MeterRegistry meterRegistry, public MetricsRestTemplateCustomizer(MeterRegistry meterRegistry,
RestTemplateExchangeTagsProvider tagProvider, String metricName) { RestTemplateExchangeTagsProvider tagProvider, String metricName) {
this(meterRegistry, tagProvider, metricName, new Autotime()); this(meterRegistry, tagProvider, metricName, AutoTimer.ENABLED);
} }
/** /**
* Creates a new {@code MetricsRestTemplateInterceptor}. When {@code autoTimeRequests} * Creates a new {@code MetricsRestTemplateInterceptor}. When {@code autoTimeRequests}
* is set to {@code true}, the interceptor records metrics using the given * is set to {@code true}, the interceptor records metrics using the given
* {@code meterRegistry} with tags provided by the given {@code tagProvider} and with * {@code meterRegistry} with tags provided by the given {@code tagProvider} and with
* {@link Autotime auto-timed request configuration}. * {@link AutoTimer auto-timed configuration}.
* @param meterRegistry the meter registry * @param meterRegistry the meter registry
* @param tagProvider the tag provider * @param tagProvider the tag provider
* @param metricName the name of the recorded metric * @param metricName the name of the recorded metric
* @param autotime auto-timed request settings * @param autoTimer the auto-timers to apply or {@code null} to disable auto-timing
* @since 2.2.0 * @since 2.2.0
*/ */
public MetricsRestTemplateCustomizer(MeterRegistry meterRegistry, public MetricsRestTemplateCustomizer(MeterRegistry meterRegistry,
RestTemplateExchangeTagsProvider tagProvider, String metricName, RestTemplateExchangeTagsProvider tagProvider, String metricName,
Autotime autotime) { AutoTimer autoTimer) {
this.interceptor = new MetricsClientHttpRequestInterceptor(meterRegistry, this.interceptor = new MetricsClientHttpRequestInterceptor(meterRegistry,
tagProvider, metricName, autotime); tagProvider, metricName, autoTimer);
} }
@Override @Override

@ -18,7 +18,7 @@ package org.springframework.boot.actuate.metrics.web.reactive.client;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.actuate.metrics.Autotime; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.boot.web.reactive.function.client.WebClientCustomizer; import org.springframework.boot.web.reactive.function.client.WebClientCustomizer;
import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.reactive.function.client.WebClient;
@ -41,12 +41,12 @@ public class MetricsWebClientCustomizer implements WebClientCustomizer {
* @param tagProvider the tag provider * @param tagProvider the tag provider
* @param metricName the name of the recorded metric * @param metricName the name of the recorded metric
* @deprecated since 2.2.0 in favor of * @deprecated since 2.2.0 in favor of
* {@link #MetricsWebClientCustomizer(MeterRegistry, WebClientExchangeTagsProvider, String, Autotime)} * {@link #MetricsWebClientCustomizer(MeterRegistry, WebClientExchangeTagsProvider, String, AutoTimer)}
*/ */
@Deprecated @Deprecated
public MetricsWebClientCustomizer(MeterRegistry meterRegistry, public MetricsWebClientCustomizer(MeterRegistry meterRegistry,
WebClientExchangeTagsProvider tagProvider, String metricName) { WebClientExchangeTagsProvider tagProvider, String metricName) {
this(meterRegistry, tagProvider, metricName, new Autotime()); this(meterRegistry, tagProvider, metricName, AutoTimer.ENABLED);
} }
/** /**
@ -56,14 +56,14 @@ public class MetricsWebClientCustomizer implements WebClientCustomizer {
* @param meterRegistry the meter registry * @param meterRegistry the meter registry
* @param tagProvider the tag provider * @param tagProvider the tag provider
* @param metricName the name of the recorded metric * @param metricName the name of the recorded metric
* @param autotime auto-timed request settings * @param autoTimer the auto-timers to apply or {@code null} to disable auto-timing
* @since 2.2.0 * @since 2.2.0
*/ */
public MetricsWebClientCustomizer(MeterRegistry meterRegistry, public MetricsWebClientCustomizer(MeterRegistry meterRegistry,
WebClientExchangeTagsProvider tagProvider, String metricName, WebClientExchangeTagsProvider tagProvider, String metricName,
Autotime autotime) { AutoTimer autoTimer) {
this.filterFunction = new MetricsWebClientFilterFunction(meterRegistry, this.filterFunction = new MetricsWebClientFilterFunction(meterRegistry,
tagProvider, metricName, autotime); tagProvider, metricName, autoTimer);
} }
@Override @Override

@ -20,10 +20,10 @@ import java.util.concurrent.TimeUnit;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.util.context.Context;
import org.springframework.boot.actuate.metrics.Autotime; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction; import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
@ -48,7 +48,7 @@ public class MetricsWebClientFilterFunction implements ExchangeFilterFunction {
private final String metricName; private final String metricName;
private final Autotime autotime; private final AutoTimer autoTimer;
/** /**
* Create a new {@code MetricsWebClientFilterFunction}. * Create a new {@code MetricsWebClientFilterFunction}.
@ -56,12 +56,12 @@ public class MetricsWebClientFilterFunction implements ExchangeFilterFunction {
* @param tagProvider provider for metrics tags * @param tagProvider provider for metrics tags
* @param metricName name of the metric to record * @param metricName name of the metric to record
* @deprecated since 2.2.0 in favor of * @deprecated since 2.2.0 in favor of
* {@link #MetricsWebClientFilterFunction(MeterRegistry, WebClientExchangeTagsProvider, String, Autotime)} * {@link #MetricsWebClientFilterFunction(MeterRegistry, WebClientExchangeTagsProvider, String, AutoTimer)}
*/ */
@Deprecated @Deprecated
public MetricsWebClientFilterFunction(MeterRegistry meterRegistry, public MetricsWebClientFilterFunction(MeterRegistry meterRegistry,
WebClientExchangeTagsProvider tagProvider, String metricName) { WebClientExchangeTagsProvider tagProvider, String metricName) {
this(meterRegistry, tagProvider, metricName, new Autotime()); this(meterRegistry, tagProvider, metricName, AutoTimer.ENABLED);
} }
/** /**
@ -69,38 +69,43 @@ public class MetricsWebClientFilterFunction implements ExchangeFilterFunction {
* @param meterRegistry the registry to which metrics are recorded * @param meterRegistry the registry to which metrics are recorded
* @param tagProvider provider for metrics tags * @param tagProvider provider for metrics tags
* @param metricName name of the metric to record * @param metricName name of the metric to record
* @param autotime auto-timed request settings * @param autoTimer the auto-timer configuration or {@code null} to disable
* @since 2.2.0 * @since 2.2.0
*/ */
public MetricsWebClientFilterFunction(MeterRegistry meterRegistry, public MetricsWebClientFilterFunction(MeterRegistry meterRegistry,
WebClientExchangeTagsProvider tagProvider, String metricName, WebClientExchangeTagsProvider tagProvider, String metricName,
Autotime autotime) { AutoTimer autoTimer) {
this.meterRegistry = meterRegistry; this.meterRegistry = meterRegistry;
this.tagProvider = tagProvider; this.tagProvider = tagProvider;
this.metricName = metricName; this.metricName = metricName;
this.autotime = (autotime != null) ? autotime : Autotime.disabled(); this.autoTimer = (autoTimer != null) ? autoTimer : AutoTimer.DISABLED;
} }
@Override @Override
public Mono<ClientResponse> filter(ClientRequest clientRequest, public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
ExchangeFunction exchangeFunction) { if (!this.autoTimer.isEnabled()) {
return exchangeFunction.exchange(clientRequest).doOnEach((signal) -> { return next.exchange(request);
if (!signal.isOnComplete() && this.autotime.isEnabled()) { }
Long startTime = signal.getContext().get(METRICS_WEBCLIENT_START_TIME); return next.exchange(request).doOnEach((signal) -> {
ClientResponse clientResponse = signal.get(); if (!signal.isOnComplete()) {
Long startTime = getStartTime(signal.getContext());
ClientResponse response = signal.get();
Throwable throwable = signal.getThrowable(); Throwable throwable = signal.getThrowable();
Iterable<Tag> tags = this.tagProvider.tags(clientRequest, clientResponse, Iterable<Tag> tags = this.tagProvider.tags(request, response, throwable);
throwable); this.autoTimer.builder(this.metricName).tags(tags)
Timer.builder(this.metricName) .description("Timer of WebClient operation")
.publishPercentiles(this.autotime.getPercentiles())
.publishPercentileHistogram(
this.autotime.isPercentilesHistogram())
.tags(tags).description("Timer of WebClient operation")
.register(this.meterRegistry) .register(this.meterRegistry)
.record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS); .record(System.nanoTime() - startTime, TimeUnit.NANOSECONDS);
} }
}).subscriberContext((context) -> context.put(METRICS_WEBCLIENT_START_TIME, }).subscriberContext(this::putStartTime);
System.nanoTime())); }
private Long getStartTime(Context context) {
return context.get(METRICS_WEBCLIENT_START_TIME);
}
private Context putStartTime(Context context) {
return context.put(METRICS_WEBCLIENT_START_TIME, System.nanoTime());
} }
} }

@ -20,11 +20,10 @@ import java.util.concurrent.TimeUnit;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag; import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import org.reactivestreams.Publisher; import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.metrics.Autotime; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.core.Ordered; import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.http.server.reactive.ServerHttpResponse;
@ -48,7 +47,7 @@ public class MetricsWebFilter implements WebFilter {
private final String metricName; private final String metricName;
private final Autotime autotime; private final AutoTimer autoTimer;
/** /**
* Create a new {@code MetricsWebFilter}. * Create a new {@code MetricsWebFilter}.
@ -57,13 +56,12 @@ public class MetricsWebFilter implements WebFilter {
* @param metricName name of the metric to record * @param metricName name of the metric to record
* @param autoTimeRequests if requests should be automatically timed * @param autoTimeRequests if requests should be automatically timed
* @deprecated since 2.2.0 in favor of * @deprecated since 2.2.0 in favor of
* {@link #MetricsWebFilter(MeterRegistry, WebFluxTagsProvider, String, Autotime)} * {@link #MetricsWebFilter(MeterRegistry, WebFluxTagsProvider, String, AutoTimer)}
*/ */
@Deprecated @Deprecated
public MetricsWebFilter(MeterRegistry registry, WebFluxTagsProvider tagsProvider, public MetricsWebFilter(MeterRegistry registry, WebFluxTagsProvider tagsProvider,
String metricName, boolean autoTimeRequests) { String metricName, boolean autoTimeRequests) {
this(registry, tagsProvider, metricName, this(registry, tagsProvider, metricName, AutoTimer.ENABLED);
new Autotime(autoTimeRequests, false, null));
} }
/** /**
@ -71,48 +69,51 @@ public class MetricsWebFilter implements WebFilter {
* @param registry the registry to which metrics are recorded * @param registry the registry to which metrics are recorded
* @param tagsProvider provider for metrics tags * @param tagsProvider provider for metrics tags
* @param metricName name of the metric to record * @param metricName name of the metric to record
* @param autotime auto timed request settings * @param autoTimer the auto-timers to apply or {@code null} to disable auto-timing
* @since 2.2.0 * @since 2.2.0
*/ */
public MetricsWebFilter(MeterRegistry registry, WebFluxTagsProvider tagsProvider, public MetricsWebFilter(MeterRegistry registry, WebFluxTagsProvider tagsProvider,
String metricName, Autotime autotime) { String metricName, AutoTimer autoTimer) {
this.registry = registry; this.registry = registry;
this.tagsProvider = tagsProvider; this.tagsProvider = tagsProvider;
this.metricName = metricName; this.metricName = metricName;
this.autotime = (autotime != null) ? autotime : Autotime.disabled(); this.autoTimer = (autoTimer != null) ? autoTimer : AutoTimer.DISABLED;
} }
@Override @Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
if (this.autotime.isEnabled()) { if (!this.autoTimer.isEnabled()) {
return chain.filter(exchange).compose((call) -> filter(exchange, call)); return chain.filter(exchange);
} }
return chain.filter(exchange); return chain.filter(exchange).compose((call) -> filter(exchange, call));
} }
private Publisher<Void> filter(ServerWebExchange exchange, Mono<Void> call) { private Publisher<Void> filter(ServerWebExchange exchange, Mono<Void> call) {
long start = System.nanoTime(); long start = System.nanoTime();
return call.doOnSuccess((done) -> onSuccess(exchange, start))
.doOnError((cause) -> onError(exchange, start, cause));
}
private void onSuccess(ServerWebExchange exchange, long start) {
record(exchange, start, null);
}
private void onError(ServerWebExchange exchange, long start, Throwable cause) {
ServerHttpResponse response = exchange.getResponse(); ServerHttpResponse response = exchange.getResponse();
return call.doOnSuccess((done) -> record(exchange, start, null)) if (response.isCommitted()) {
.doOnError((cause) -> { record(exchange, start, cause);
if (response.isCommitted()) { }
record(exchange, start, cause); else {
} response.beforeCommit(() -> {
else { record(exchange, start, cause);
response.beforeCommit(() -> { return Mono.empty();
record(exchange, start, cause); });
return Mono.empty(); }
});
}
});
} }
private void record(ServerWebExchange exchange, long start, Throwable cause) { private void record(ServerWebExchange exchange, long start, Throwable cause) {
Iterable<Tag> tags = this.tagsProvider.httpRequestTags(exchange, cause); Iterable<Tag> tags = this.tagsProvider.httpRequestTags(exchange, cause);
Timer.builder(this.metricName).tags(tags) this.autoTimer.builder(this.metricName).tags(tags).register(this.registry)
.publishPercentiles(this.autotime.getPercentiles())
.publishPercentileHistogram(this.autotime.isPercentilesHistogram())
.register(this.registry)
.record(System.nanoTime() - start, TimeUnit.NANOSECONDS); .record(System.nanoTime() - start, TimeUnit.NANOSECONDS);
} }

@ -20,7 +20,6 @@ import java.io.IOException;
import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedElement;
import java.util.Collections; import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
import javax.servlet.FilterChain; import javax.servlet.FilterChain;
import javax.servlet.ServletException; import javax.servlet.ServletException;
@ -29,12 +28,11 @@ import javax.servlet.http.HttpServletResponse;
import io.micrometer.core.annotation.Timed; import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.Timer.Builder; import io.micrometer.core.instrument.Timer.Builder;
import io.micrometer.core.instrument.Timer.Sample; import io.micrometer.core.instrument.Timer.Sample;
import org.springframework.boot.actuate.metrics.Autotime; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.core.annotation.MergedAnnotationCollectors; import org.springframework.core.annotation.MergedAnnotationCollectors;
import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -60,7 +58,7 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
private final String metricName; private final String metricName;
private final Autotime autotime; private final AutoTimer autoTimer;
/** /**
* Create a new {@link WebMvcMetricsFilter} instance. * Create a new {@link WebMvcMetricsFilter} instance.
@ -70,13 +68,12 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
* @param autoTimeRequests if requests should be automatically timed * @param autoTimeRequests if requests should be automatically timed
* @since 2.0.7 * @since 2.0.7
* @deprecated since 2.2.0 in favor of * @deprecated since 2.2.0 in favor of
* {@link #WebMvcMetricsFilter(MeterRegistry, WebMvcTagsProvider, String, Autotime)} * {@link #WebMvcMetricsFilter(MeterRegistry, WebMvcTagsProvider, String, AutoTimer)}
*/ */
@Deprecated @Deprecated
public WebMvcMetricsFilter(MeterRegistry registry, WebMvcTagsProvider tagsProvider, public WebMvcMetricsFilter(MeterRegistry registry, WebMvcTagsProvider tagsProvider,
String metricName, boolean autoTimeRequests) { String metricName, boolean autoTimeRequests) {
this(registry, tagsProvider, metricName, this(registry, tagsProvider, metricName, AutoTimer.ENABLED);
new Autotime(autoTimeRequests, false, null));
} }
/** /**
@ -84,15 +81,15 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
* @param registry the meter registry * @param registry the meter registry
* @param tagsProvider the tags provider * @param tagsProvider the tags provider
* @param metricName the metric name * @param metricName the metric name
* @param autotime auto timed request settings * @param autoTimer the auto-timers to apply or {@code null} to disable auto-timing
* @since 2.2.0 * @since 2.2.0
*/ */
public WebMvcMetricsFilter(MeterRegistry registry, WebMvcTagsProvider tagsProvider, public WebMvcMetricsFilter(MeterRegistry registry, WebMvcTagsProvider tagsProvider,
String metricName, Autotime autotime) { String metricName, AutoTimer autoTimer) {
this.registry = registry; this.registry = registry;
this.tagsProvider = tagsProvider; this.tagsProvider = tagsProvider;
this.metricName = metricName; this.metricName = metricName;
this.autotime = autotime; this.autoTimer = autoTimer;
} }
@Override @Override
@ -104,12 +101,6 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
protected void doFilterInternal(HttpServletRequest request, protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain) HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException { throws ServletException, IOException {
filterAndRecordMetrics(request, response, filterChain);
}
private void filterAndRecordMetrics(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
TimingContext timingContext = TimingContext.get(request); TimingContext timingContext = TimingContext.get(request);
if (timingContext == null) { if (timingContext == null) {
timingContext = startAndAttachTimingContext(request); timingContext = startAndAttachTimingContext(request);
@ -123,16 +114,16 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
// TimingContext that was attached to the first) // TimingContext that was attached to the first)
Throwable exception = (Throwable) request Throwable exception = (Throwable) request
.getAttribute(DispatcherServlet.EXCEPTION_ATTRIBUTE); .getAttribute(DispatcherServlet.EXCEPTION_ATTRIBUTE);
record(timingContext, response, request, exception); record(timingContext, request, response, exception);
} }
} }
catch (NestedServletException ex) { catch (NestedServletException ex) {
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
record(timingContext, response, request, ex.getCause()); record(timingContext, request, response, ex.getCause());
throw ex; throw ex;
} }
catch (ServletException | IOException | RuntimeException ex) { catch (ServletException | IOException | RuntimeException ex) {
record(timingContext, response, request, ex); record(timingContext, request, response, ex);
throw ex; throw ex;
} }
} }
@ -144,6 +135,26 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
return timingContext; return timingContext;
} }
private void record(TimingContext timingContext, HttpServletRequest request,
HttpServletResponse response, Throwable exception) {
Object handler = getHandler(request);
Set<Timed> annotations = getTimedAnnotations(handler);
Timer.Sample timerSample = timingContext.getTimerSample();
if (annotations.isEmpty()) {
Builder builder = this.autoTimer.builder(this.metricName);
timerSample.stop(getTimer(builder, handler, request, response, exception));
return;
}
for (Timed annotation : annotations) {
Builder builder = Timer.builder(annotation, this.metricName);
timerSample.stop(getTimer(builder, handler, request, response, exception));
}
}
private Object getHandler(HttpServletRequest request) {
return request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
}
private Set<Timed> getTimedAnnotations(Object handler) { private Set<Timed> getTimedAnnotations(Object handler) {
if (!(handler instanceof HandlerMethod)) { if (!(handler instanceof HandlerMethod)) {
return Collections.emptySet(); return Collections.emptySet();
@ -152,45 +163,27 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter {
} }
private Set<Timed> getTimedAnnotations(HandlerMethod handler) { private Set<Timed> getTimedAnnotations(HandlerMethod handler) {
Set<Timed> timed = findTimedAnnotations(handler.getMethod()); Set<Timed> methodAnnotations = findTimedAnnotations(handler.getMethod());
if (timed.isEmpty()) { if (!methodAnnotations.isEmpty()) {
return findTimedAnnotations(handler.getBeanType()); return methodAnnotations;
} }
return timed; return findTimedAnnotations(handler.getBeanType());
} }
private Set<Timed> findTimedAnnotations(AnnotatedElement element) { private Set<Timed> findTimedAnnotations(AnnotatedElement element) {
return MergedAnnotations.from(element).stream(Timed.class) MergedAnnotations annotations = MergedAnnotations.from(element);
.collect(MergedAnnotationCollectors.toAnnotationSet()); if (!annotations.isPresent(Timed.class)) {
} return Collections.emptySet();
private void record(TimingContext timingContext, HttpServletResponse response,
HttpServletRequest request, Throwable exception) {
Object handlerObject = request
.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
Set<Timed> annotations = getTimedAnnotations(handlerObject);
Timer.Sample timerSample = timingContext.getTimerSample();
Supplier<Iterable<Tag>> tags = () -> this.tagsProvider.getTags(request, response,
handlerObject, exception);
if (annotations.isEmpty()) {
if (this.autotime.isEnabled()) {
stop(timerSample, tags,
Timer.builder(this.metricName)
.publishPercentiles(this.autotime.getPercentiles())
.publishPercentileHistogram(
this.autotime.isPercentilesHistogram()));
}
}
else {
for (Timed annotation : annotations) {
stop(timerSample, tags, Timer.builder(annotation, this.metricName));
}
} }
return annotations.stream(Timed.class)
.collect(MergedAnnotationCollectors.toAnnotationSet());
} }
private void stop(Timer.Sample timerSample, Supplier<Iterable<Tag>> tags, private Timer getTimer(Builder builder, Object handler, HttpServletRequest request,
Builder builder) { HttpServletResponse response, Throwable exception) {
timerSample.stop(builder.tags(tags.get()).register(this.registry)); return builder
.tags(this.tagsProvider.getTags(request, response, handler, exception))
.register(this.registry);
} }
/** /**

@ -27,7 +27,7 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.metrics.Autotime; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer; import org.springframework.test.web.client.MockRestServiceServer;
@ -60,7 +60,7 @@ public class MetricsRestTemplateCustomizerTests {
this.mockServer = MockRestServiceServer.createServer(this.restTemplate); this.mockServer = MockRestServiceServer.createServer(this.restTemplate);
this.customizer = new MetricsRestTemplateCustomizer(this.registry, this.customizer = new MetricsRestTemplateCustomizer(this.registry,
new DefaultRestTemplateExchangeTagsProvider(), "http.client.requests", new DefaultRestTemplateExchangeTagsProvider(), "http.client.requests",
new Autotime()); AutoTimer.ENABLED);
this.customizer.customize(this.restTemplate); this.customizer.customize(this.restTemplate);
} }

@ -30,7 +30,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.metrics.Autotime; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.client.ClientRequest; import org.springframework.web.reactive.function.client.ClientRequest;
@ -65,7 +65,7 @@ public class MetricsWebClientFilterFunctionTests {
this.registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock()); this.registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, new MockClock());
this.filterFunction = new MetricsWebClientFilterFunction(this.registry, this.filterFunction = new MetricsWebClientFilterFunction(this.registry,
new DefaultWebClientExchangeTagsProvider(), "http.client.requests", new DefaultWebClientExchangeTagsProvider(), "http.client.requests",
new Autotime()); AutoTimer.ENABLED);
this.response = mock(ClientResponse.class); this.response = mock(ClientResponse.class);
this.exchange = (r) -> Mono.just(this.response); this.exchange = (r) -> Mono.just(this.response);
} }

@ -25,7 +25,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import org.springframework.boot.actuate.metrics.Autotime; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange; import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.reactive.HandlerMapping; import org.springframework.web.reactive.HandlerMapping;
@ -51,7 +51,8 @@ public class MetricsWebFilterTests {
MockClock clock = new MockClock(); MockClock clock = new MockClock();
this.registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock); this.registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, clock);
this.webFilter = new MetricsWebFilter(this.registry, this.webFilter = new MetricsWebFilter(this.registry,
new DefaultWebFluxTagsProvider(), REQUEST_METRICS_NAME, new Autotime()); new DefaultWebFluxTagsProvider(), REQUEST_METRICS_NAME,
AutoTimer.ENABLED);
} }
@Test @Test

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.metrics.web.servlet; package org.springframework.boot.actuate.metrics.web.servlet;
import java.util.Arrays;
import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.MockClock; import io.micrometer.core.instrument.MockClock;
@ -30,7 +28,6 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.Autotime;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -108,7 +105,8 @@ public class WebMvcMetricsFilterAutoTimedTests {
MeterRegistry registry) { MeterRegistry registry) {
return new WebMvcMetricsFilter(registry, new DefaultWebMvcTagsProvider(), return new WebMvcMetricsFilter(registry, new DefaultWebMvcTagsProvider(),
"http.server.requests", "http.server.requests",
new Autotime(true, true, Arrays.asList(0.5, 0.95))); (builder) -> builder.publishPercentiles(0.5, 0.95)
.publishPercentileHistogram(true));
} }
} }

@ -56,7 +56,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.metrics.Autotime; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
@ -373,7 +373,7 @@ public class WebMvcMetricsFilterTests {
WebMvcMetricsFilter webMetricsFilter(MeterRegistry registry, WebMvcMetricsFilter webMetricsFilter(MeterRegistry registry,
WebApplicationContext ctx) { WebApplicationContext ctx) {
return new WebMvcMetricsFilter(registry, new DefaultWebMvcTagsProvider(), return new WebMvcMetricsFilter(registry, new DefaultWebMvcTagsProvider(),
"http.server.requests", new Autotime()); "http.server.requests", AutoTimer.ENABLED);
} }
} }

@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.metrics.Autotime; import org.springframework.boot.actuate.metrics.AutoTimer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -112,7 +112,7 @@ public class WebMvcMetricsIntegrationTests {
public WebMvcMetricsFilter webMetricsFilter(MeterRegistry registry, public WebMvcMetricsFilter webMetricsFilter(MeterRegistry registry,
WebApplicationContext ctx) { WebApplicationContext ctx) {
return new WebMvcMetricsFilter(registry, new DefaultWebMvcTagsProvider(), return new WebMvcMetricsFilter(registry, new DefaultWebMvcTagsProvider(),
"http.server.requests", new Autotime()); "http.server.requests", AutoTimer.ENABLED);
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)

Loading…
Cancel
Save