Skip to content

Commit 956afdc

Browse files
committed
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
1 parent b337f67 commit 956afdc

File tree

7 files changed

+213
-1
lines changed

7 files changed

+213
-1
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/WebClientMetricsConfiguration.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@
1717
package org.springframework.boot.actuate.autoconfigure.metrics.web.client;
1818

1919
import io.micrometer.core.instrument.MeterRegistry;
20+
import reactor.netty.http.client.HttpClient;
2021

2122
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
2223
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Web.Client.ClientRequest;
@@ -25,6 +26,7 @@
2526
import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
2627
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
2728
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
29+
import org.springframework.boot.autoconfigure.web.reactive.function.client.ReactorNettyHttpClientMapper;
2830
import org.springframework.context.annotation.Bean;
2931
import org.springframework.context.annotation.Configuration;
3032
import org.springframework.web.reactive.function.client.WebClient;
@@ -53,4 +55,14 @@ MetricsWebClientCustomizer metricsWebClientCustomizer(MeterRegistry meterRegistr
5355
request.getAutotime());
5456
}
5557

58+
@ConditionalOnClass(HttpClient.class)
59+
static class ReactorNettyClientMetricsConfiguration {
60+
61+
@Bean
62+
ReactorNettyHttpClientMapper metricsHttpClientMapper() {
63+
return (httpClient) -> httpClient.tcpConfiguration((tcpClient) -> tcpClient.metrics(true));
64+
}
65+
66+
}
67+
5668
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.metrics.web.netty;
18+
19+
import io.micrometer.core.instrument.MeterRegistry;
20+
import reactor.netty.http.server.HttpServer;
21+
22+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
24+
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
25+
import org.springframework.boot.web.embedded.netty.NettyServerCustomizer;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.context.annotation.Configuration;
28+
29+
/**
30+
* {@link EnableAutoConfiguration Auto-configuration} for Reactor Netty metrics.
31+
*
32+
* @author Brian Clozel
33+
* @since 2.3.0
34+
*/
35+
@Configuration(proxyBeanMethods = false)
36+
@ConditionalOnWebApplication
37+
@ConditionalOnClass({ MeterRegistry.class, HttpServer.class })
38+
public class NettyMetricsAutoConfiguration {
39+
40+
@Bean
41+
public NettyServerCustomizer nettyServerMetricsCustomizer() {
42+
return (httpServer) -> httpServer.tcpConfiguration((tcpServer) -> tcpServer.metrics(true));
43+
}
44+
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Auto-configuration for Reactor Netty actuator metrics.
19+
*/
20+
package org.springframework.boot.actuate.autoconfigure.metrics.web.netty;

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsA
7171
org.springframework.boot.actuate.autoconfigure.metrics.r2dbc.ConnectionPoolMetricsAutoConfiguration,\
7272
org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration,\
7373
org.springframework.boot.actuate.autoconfigure.metrics.web.jetty.JettyMetricsAutoConfiguration,\
74+
org.springframework.boot.actuate.autoconfigure.metrics.web.netty.NettyMetricsAutoConfiguration,\
7475
org.springframework.boot.actuate.autoconfigure.metrics.web.reactive.WebFluxMetricsAutoConfiguration,\
7576
org.springframework.boot.actuate.autoconfigure.metrics.web.servlet.WebMvcMetricsAutoConfiguration,\
7677
org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat.TomcatMetricsAutoConfiguration,\

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/client/WebClientMetricsConfigurationTests.java

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,25 @@
1818

1919
import java.time.Duration;
2020

21+
import io.micrometer.core.instrument.Clock;
2122
import io.micrometer.core.instrument.MeterRegistry;
23+
import io.micrometer.core.instrument.Metrics;
2224
import io.micrometer.core.instrument.Timer;
25+
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
2326
import io.micrometer.core.instrument.distribution.HistogramSnapshot;
27+
import io.micrometer.core.instrument.simple.SimpleConfig;
28+
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
29+
import okhttp3.mockwebserver.MockResponse;
30+
import okhttp3.mockwebserver.MockWebServer;
2431
import org.junit.jupiter.api.Test;
2532
import org.junit.jupiter.api.extension.ExtendWith;
2633
import reactor.core.publisher.Mono;
34+
import reactor.netty.http.client.HttpClient;
2735

2836
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
2937
import org.springframework.boot.actuate.metrics.web.reactive.client.WebClientExchangeTagsProvider;
3038
import org.springframework.boot.autoconfigure.AutoConfigurations;
39+
import org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration;
3140
import org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration;
3241
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
3342
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -107,6 +116,47 @@ void autoTimeRequestsCanBeConfigured() {
107116
});
108117
}
109118

119+
@Test
120+
void shouldConfigureMetricsForReactorNetty() {
121+
this.contextRunner.withConfiguration(AutoConfigurations.of(ClientHttpConnectorAutoConfiguration.class))
122+
.run((context) -> {
123+
CompositeMeterRegistry registry = Metrics.globalRegistry;
124+
MockWebServer server = new MockWebServer();
125+
try {
126+
server.start();
127+
server.enqueue(new MockResponse());
128+
String serverAddress = "http://" + server.getHostName() + ":" + server.getPort();
129+
WebClient.Builder builder = context.getBean(WebClient.Builder.class);
130+
WebClient client = builder.baseUrl(serverAddress).build();
131+
assertThat(registry.find("reactor.netty.tcp.client.connect.time").timer()).isNull();
132+
client.get().uri("/test").retrieve().toBodilessEntity().block();
133+
assertThat(registry.find("reactor.netty.tcp.client.connect.time").timer()).isNotNull();
134+
}
135+
finally {
136+
server.shutdown();
137+
}
138+
});
139+
}
140+
141+
@Test
142+
void sanityTest() throws Exception {
143+
MockWebServer server = new MockWebServer();
144+
try {
145+
server.start();
146+
server.enqueue(new MockResponse());
147+
SimpleMeterRegistry registry = new SimpleMeterRegistry(SimpleConfig.DEFAULT, Clock.SYSTEM);
148+
Metrics.addRegistry(registry);
149+
HttpClient client = HttpClient.create().metrics(true);
150+
assertThat(registry.find("reactor.netty.http.client.connect.time").timer()).isNull();
151+
client.get().uri("http://" + server.getHostName() + ":" + server.getPort()).response().block();
152+
assertThat(registry.find("reactor.netty.http.client.connect.time").timer()).isNotNull();
153+
}
154+
finally {
155+
server.shutdown();
156+
}
157+
158+
}
159+
110160
private MeterRegistry getInitializedMeterRegistry(AssertableApplicationContext context) {
111161
WebClient webClient = mockWebClient(context.getBean(WebClient.Builder.class));
112162
MeterRegistry registry = context.getBean(MeterRegistry.class);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2012-2020 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.metrics.web.netty;
18+
19+
import java.util.stream.Collectors;
20+
21+
import io.micrometer.core.instrument.MeterRegistry;
22+
import io.micrometer.core.instrument.Metrics;
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.beans.factory.ObjectProvider;
26+
import org.springframework.boot.SpringApplication;
27+
import org.springframework.boot.autoconfigure.AutoConfigurations;
28+
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration;
29+
import org.springframework.boot.context.event.ApplicationStartedEvent;
30+
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
31+
import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory;
32+
import org.springframework.boot.web.embedded.netty.NettyServerCustomizer;
33+
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
34+
import org.springframework.context.annotation.Bean;
35+
import org.springframework.context.annotation.Configuration;
36+
import org.springframework.http.server.reactive.HttpHandler;
37+
import org.springframework.web.reactive.function.client.WebClient;
38+
39+
import static org.assertj.core.api.Assertions.assertThat;
40+
41+
/**
42+
* Tests for {@link NettyMetricsAutoConfiguration}
43+
*
44+
* @author Brian Clozel
45+
*/
46+
class NettyMetricsAutoConfigurationTests {
47+
48+
@Test
49+
void autoConfiguresTcpMetricsWithReactorNettyServer() {
50+
MeterRegistry registry = Metrics.globalRegistry;
51+
assertThat(registry.find("reactor.netty.tcp.server.data.received").summary()).isNull();
52+
new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new)
53+
.withConfiguration(AutoConfigurations.of(NettyMetricsAutoConfiguration.class,
54+
ReactiveWebServerFactoryAutoConfiguration.class))
55+
.withUserConfiguration(ReactiveWebServerConfiguration.class).run((context) -> {
56+
AnnotationConfigReactiveWebServerApplicationContext serverContext = context
57+
.getSourceApplicationContext(AnnotationConfigReactiveWebServerApplicationContext.class);
58+
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
59+
context.getSourceApplicationContext()));
60+
WebClient.create("http://localhost:" + serverContext.getWebServer().getPort()).get().retrieve()
61+
.toBodilessEntity().block();
62+
assertThat(registry.find("reactor.netty.tcp.server.data.received").summary()).isNotNull();
63+
});
64+
}
65+
66+
@Configuration(proxyBeanMethods = false)
67+
static class ReactiveWebServerConfiguration {
68+
69+
@Bean
70+
NettyReactiveWebServerFactory nettyFactory(ObjectProvider<NettyServerCustomizer> customizers) {
71+
NettyReactiveWebServerFactory serverFactory = new NettyReactiveWebServerFactory(0);
72+
serverFactory.setServerCustomizers(customizers.orderedStream().collect(Collectors.toList()));
73+
return serverFactory;
74+
}
75+
76+
@Bean
77+
HttpHandler httpHandler() {
78+
return (req, res) -> res.setComplete();
79+
}
80+
81+
}
82+
83+
}

spring-boot-project/spring-boot-docs/src/docs/asciidoc/production-ready-features.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1813,6 +1813,7 @@ Spring Boot registers the following core metrics when applicable:
18131813
* Logback metrics: record the number of events logged to Logback at each level
18141814
* Uptime metrics: report a gauge for uptime and a fixed gauge representing the application's absolute start time
18151815
* Tomcat metrics (`server.tomcat.mbeanregistry.enabled` must be set to `true` for all Tomcat metrics to be registered)
1816+
* Reactor Netty metrics (TCP and allocator metrics for client and server)
18161817
* {spring-integration-docs}system-management.html#micrometer-integration[Spring Integration] metrics
18171818

18181819

0 commit comments

Comments
 (0)