Add support for Reactor Netty Micrometer metrics
This commit enables the production of TCP and buffer allocator metrics for Reactor Netty, client and server. When applications use auto-configured server (`NettyReactiveWebServerFactory`) and client (through `WebClient.Builder`) instances, metrics will be enabled. Note that HTTP metrics are not enabled here, since similar metrics are already produced at the WebFlux level. Also, to avoid cardinality explosion of metrics (through the URI tag), Reactor Netty offers configurable infrastructure to deduplicate URI tags by turning expanded URI instances into templated URIs. This is not targeted for Spring usage. Closes gh-19388
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2019 the original author or authors.
|
||||
* Copyright 2012-2020 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics.web.client;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Client.ClientRequest;
|
||||
@@ -25,6 +26,7 @@ import org.springframework.boot.actuate.metrics.web.reactive.client.MetricsWebCl
|
||||
import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.function.client.ReactorNettyHttpClientMapper;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
@@ -53,4 +55,14 @@ class WebClientMetricsConfiguration {
|
||||
request.getAutotime());
|
||||
}
|
||||
|
||||
@ConditionalOnClass(HttpClient.class)
|
||||
static class ReactorNettyClientMetricsConfiguration {
|
||||
|
||||
@Bean
|
||||
ReactorNettyHttpClientMapper metricsHttpClientMapper() {
|
||||
return (httpClient) -> httpClient.tcpConfiguration((tcpClient) -> tcpClient.metrics(true));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.autoconfigure.metrics.web.netty;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import reactor.netty.http.server.HttpServer;
|
||||
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
|
||||
import org.springframework.boot.web.embedded.netty.NettyServerCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for Reactor Netty metrics.
|
||||
*
|
||||
* @author Brian Clozel
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnWebApplication
|
||||
@ConditionalOnClass({ MeterRegistry.class, HttpServer.class })
|
||||
public class NettyMetricsAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
public NettyServerCustomizer nettyServerMetricsCustomizer() {
|
||||
return (httpServer) -> httpServer.tcpConfiguration((tcpServer) -> tcpServer.metrics(true));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Auto-configuration for Reactor Netty actuator metrics.
|
||||
*/
|
||||
package org.springframework.boot.actuate.autoconfigure.metrics.web.netty;
|
||||
@@ -71,6 +71,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsA
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.r2dbc.ConnectionPoolMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.web.jetty.JettyMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.web.netty.NettyMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration,\
|
||||
org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat.TomcatMetricsAutoConfiguration,\
|
||||
|
||||
@@ -18,16 +18,25 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.client;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import io.micrometer.core.instrument.Clock;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import io.micrometer.core.instrument.Timer;
|
||||
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
|
||||
import io.micrometer.core.instrument.distribution.HistogramSnapshot;
|
||||
import io.micrometer.core.instrument.simple.SimpleConfig;
|
||||
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.netty.http.client.HttpClient;
|
||||
|
||||
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
|
||||
import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
|
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
|
||||
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
|
||||
@@ -107,6 +116,47 @@ class WebClientMetricsConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConfigureMetricsForReactorNetty() {
|
||||
this.contextRunner.withConfiguration(AutoConfigurations.of(ClientHttpConnectorAutoConfiguration.class))
|
||||
.run((context) -> {
|
||||
CompositeMeterRegistry registry = Metrics.globalRegistry;
|
||||
MockWebServer server = new MockWebServer();
|
||||
try {
|
||||
server.start();
|
||||
server.enqueue(new MockResponse());
|
||||
String serverAddress = "http://" + server.getHostName() + ":" + server.getPort();
|
||||
WebClient.Builder builder = context.getBean(WebClient.Builder.class);
|
||||
WebClient client = builder.baseUrl(serverAddress).build();
|
||||
assertThat(registry.find("reactor.netty.tcp.client.connect.time").timer()).isNull();
|
||||
client.get().uri("/test").retrieve().toBodilessEntity().block();
|
||||
assertThat(registry.find("reactor.netty.tcp.client.connect.time").timer()).isNotNull();
|
||||
}
|
||||
finally {
|
||||
server.shutdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void sanityTest() throws Exception {
|
||||
MockWebServer server = new MockWebServer();
|
||||
try {
|
||||
server.start();
|
||||
server.enqueue(new MockResponse());
|
||||
SimpleMeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, Clock.SYSTEM);
|
||||
Metrics.addRegistry(registry);
|
||||
HttpClient client = HttpClient.create().metrics(true);
|
||||
assertThat(registry.find("reactor.netty.http.client.connect.time").timer()).isNull();
|
||||
client.get().uri("http://" + server.getHostName() + ":" + server.getPort()).response().block();
|
||||
assertThat(registry.find("reactor.netty.http.client.connect.time").timer()).isNotNull();
|
||||
}
|
||||
finally {
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private MeterRegistry getInitializedMeterRegistry(AssertableApplicationContext context) {
|
||||
WebClient webClient = mockWebClient(context.getBean(WebClient.Builder.class));
|
||||
MeterRegistry registry = context.getBean(MeterRegistry.class);
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2012-2020 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.autoconfigure.metrics.web.netty;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.Metrics;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigurations;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration;
|
||||
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
|
||||
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
|
||||
import org.springframework.boot.web.embedded.netty.NettyServerCustomizer;
|
||||
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.server.reactive.HttpHandler;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests for {@link NettyMetricsAutoConfiguration}
|
||||
*
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
class NettyMetricsAutoConfigurationTests {
|
||||
|
||||
@Test
|
||||
void autoConfiguresTcpMetricsWithReactorNettyServer() {
|
||||
MeterRegistry registry = Metrics.globalRegistry;
|
||||
assertThat(registry.find("reactor.netty.tcp.server.data.received").summary()).isNull();
|
||||
new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new)
|
||||
.withConfiguration(AutoConfigurations.of(NettyMetricsAutoConfiguration.class,
|
||||
ReactiveWebServerFactoryAutoConfiguration.class))
|
||||
.withUserConfiguration(ReactiveWebServerConfiguration.class).run((context) -> {
|
||||
AnnotationConfigReactiveWebServerApplicationContext serverContext = context
|
||||
.getSourceApplicationContext(AnnotationConfigReactiveWebServerApplicationContext.class);
|
||||
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
|
||||
context.getSourceApplicationContext()));
|
||||
WebClient.create("http://localhost:" + serverContext.getWebServer().getPort()).get().retrieve()
|
||||
.toBodilessEntity().block();
|
||||
assertThat(registry.find("reactor.netty.tcp.server.data.received").summary()).isNotNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class ReactiveWebServerConfiguration {
|
||||
|
||||
@Bean
|
||||
NettyReactiveWebServerFactory nettyFactory(ObjectProvider<NettyServerCustomizer> customizers) {
|
||||
NettyReactiveWebServerFactory serverFactory = new NettyReactiveWebServerFactory(0);
|
||||
serverFactory.setServerCustomizers(customizers.orderedStream().collect(Collectors.toList()));
|
||||
return serverFactory;
|
||||
}
|
||||
|
||||
@Bean
|
||||
HttpHandler httpHandler() {
|
||||
return (req, res) -> res.setComplete();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1813,6 +1813,7 @@ Spring Boot registers the following core metrics when applicable:
|
||||
* Logback metrics: record the number of events logged to Logback at each level
|
||||
* Uptime metrics: report a gauge for uptime and a fixed gauge representing the application's absolute start time
|
||||
* Tomcat metrics (`server.tomcat.mbeanregistry.enabled` must be set to `true` for all Tomcat metrics to be registered)
|
||||
* Reactor Netty metrics (TCP and allocator metrics for client and server)
|
||||
* {spring-integration-docs}system-management.html#micrometer-integration[Spring Integration] metrics
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user