Merge pull request #26418 from bono007

* gh-26418:
  Polish "Auto-configure Jetty connection and SSL metrics"
  Auto-configure Jetty connection and SSL metrics

Closes gh-26418
This commit is contained in:
Andy Wilkinson
2021-07-13 14:01:23 +01:00
7 changed files with 338 additions and 24 deletions

View File

@@ -17,16 +17,21 @@
package org.springframework.boot.actuate.autoconfigure.metrics.web.jetty;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.jetty.JettyConnectionMetrics;
import io.micrometer.core.instrument.binder.jetty.JettyServerThreadPoolMetrics;
import io.micrometer.core.instrument.binder.jetty.JettySslHandshakeMetrics;
import org.eclipse.jetty.server.Server;
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
import org.springframework.boot.actuate.metrics.web.jetty.JettyConnectionMetricsBinder;
import org.springframework.boot.actuate.metrics.web.jetty.JettyServerThreadPoolMetricsBinder;
import org.springframework.boot.actuate.metrics.web.jetty.JettySslHandshakeMetricsBinder;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
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.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -35,6 +40,7 @@ import org.springframework.context.annotation.Configuration;
* {@link EnableAutoConfiguration Auto-configuration} for Jetty metrics.
*
* @author Andy Wilkinson
* @author Chris Bono
* @since 2.1.0
*/
@Configuration(proxyBeanMethods = false)
@@ -50,4 +56,19 @@ public class JettyMetricsAutoConfiguration {
return new JettyServerThreadPoolMetricsBinder(meterRegistry);
}
@Bean
@ConditionalOnBean(MeterRegistry.class)
@ConditionalOnMissingBean({ JettyConnectionMetrics.class, JettyConnectionMetricsBinder.class })
public JettyConnectionMetricsBinder jettyConnectionMetricsBinder(MeterRegistry meterRegistry) {
return new JettyConnectionMetricsBinder(meterRegistry);
}
@Bean
@ConditionalOnBean(MeterRegistry.class)
@ConditionalOnMissingBean({ JettySslHandshakeMetrics.class, JettySslHandshakeMetricsBinder.class })
@ConditionalOnProperty(name = "server.ssl.enabled", havingValue = "true")
public JettySslHandshakeMetricsBinder jettySslHandshakeMetricsBinder(MeterRegistry meterRegistry) {
return new JettySslHandshakeMetricsBinder(meterRegistry);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2021 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,11 +17,14 @@
package org.springframework.boot.actuate.autoconfigure.metrics.web.jetty;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.actuate.metrics.web.jetty.JettyConnectionMetricsBinder;
import org.springframework.boot.actuate.metrics.web.jetty.JettyServerThreadPoolMetricsBinder;
import org.springframework.boot.actuate.metrics.web.jetty.JettySslHandshakeMetricsBinder;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
@@ -43,6 +46,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link JettyMetricsAutoConfiguration}.
*
* @author Andy Wilkinson
* @author Chris Bono
*/
class JettyMetricsAutoConfigurationTests {
@@ -83,6 +87,132 @@ class JettyMetricsAutoConfigurationTests {
.hasBean("customJettyServerThreadPoolMetricsBinder"));
}
@Test
void autoConfiguresConnectionMetricsWithEmbeddedServletJetty() {
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext()));
assertThat(context).hasSingleBean(JettyConnectionMetricsBinder.class);
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.connections.messages.in").meter()).isNotNull();
});
}
@Test
void autoConfiguresConnectionMetricsWithEmbeddedReactiveJetty() {
new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class,
ReactiveWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class)
.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext()));
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.connections.messages.in").meter()).isNotNull();
});
}
@Test
void allowsCustomJettyConnectionMetricsBinderToBeUsed() {
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ServletWebServerConfiguration.class, CustomJettyConnectionMetricsBinder.class,
MeterRegistryConfiguration.class)
.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext()));
assertThat(context).hasSingleBean(JettyConnectionMetricsBinder.class)
.hasBean("customJettyConnectionMetricsBinder");
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.connections.messages.in").tag("custom-tag-name", "custom-tag-value")
.meter()).isNotNull();
});
}
@Test
void autoConfiguresSslHandshakeMetricsWithEmbeddedServletJetty() {
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
.withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks",
"server.ssl.key-store-password: secret", "server.ssl.key-password: password")
.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext()));
assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class);
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.ssl.handshakes").meter()).isNotNull();
});
}
@Test
void autoConfiguresSslHandshakeMetricsWithEmbeddedReactiveJetty() {
new ReactiveWebApplicationContextRunner(AnnotationConfigReactiveWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class,
ReactiveWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class)
.withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks",
"server.ssl.key-store-password: secret", "server.ssl.key-password: password")
.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext()));
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.ssl.handshakes").meter()).isNotNull();
});
}
@Test
void allowsCustomJettySslHandshakeMetricsBinderToBeUsed() {
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ServletWebServerConfiguration.class, CustomJettySslHandshakeMetricsBinder.class,
MeterRegistryConfiguration.class)
.withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks",
"server.ssl.key-store-password: secret", "server.ssl.key-password: password")
.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext()));
assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class)
.hasBean("customJettySslHandshakeMetricsBinder");
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.ssl.handshakes").tag("custom-tag-name", "custom-tag-value").meter())
.isNotNull();
});
new WebApplicationContextRunner().withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class))
.withUserConfiguration(CustomJettySslHandshakeMetricsBinder.class, MeterRegistryConfiguration.class)
.withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks",
"server.ssl.key-store-password: secret", "server.ssl.key-password: password")
.run((context) -> assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class)
.hasBean("customJettySslHandshakeMetricsBinder"));
}
@Test
void doesNotAutoConfigureSslHandshakeMetricsWhenSslEnabledPropertyNotSpecified() {
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
.run((context) -> assertThat(context).doesNotHaveBean(JettySslHandshakeMetricsBinder.class));
}
@Test
void doesNotAutoConfigureSslHandshakeMetricsWhenSslEnabledPropertySetToFalse() {
new WebApplicationContextRunner(AnnotationConfigServletWebServerApplicationContext::new)
.withConfiguration(AutoConfigurations.of(JettyMetricsAutoConfiguration.class,
ServletWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
.withPropertyValues("server.ssl.enabled: false")
.run((context) -> assertThat(context).doesNotHaveBean(JettySslHandshakeMetricsBinder.class));
}
@Configuration(proxyBeanMethods = false)
static class MeterRegistryConfiguration {
@@ -128,4 +258,24 @@ class JettyMetricsAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class CustomJettyConnectionMetricsBinder {
@Bean
JettyConnectionMetricsBinder customJettyConnectionMetricsBinder(MeterRegistry meterRegistry) {
return new JettyConnectionMetricsBinder(meterRegistry, Tags.of("custom-tag-name", "custom-tag-value"));
}
}
@Configuration(proxyBeanMethods = false)
static class CustomJettySslHandshakeMetricsBinder {
@Bean
JettySslHandshakeMetricsBinder customJettySslHandshakeMetricsBinder(MeterRegistry meterRegistry) {
return new JettySslHandshakeMetricsBinder(meterRegistry, Tags.of("custom-tag-name", "custom-tag-value"));
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright 2012-2021 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.web.jetty;
import org.eclipse.jetty.server.Server;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.boot.web.context.WebServerApplicationContext;
import org.springframework.boot.web.embedded.jetty.JettyWebServer;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
/**
* Base class for binding Jetty metrics in response to an {@link ApplicationStartedEvent}.
*
* @author Andy Wilkinson
* @since 2.6.0
*/
public abstract class AbstractJettyMetricsBinder implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
Server server = findServer(event.getApplicationContext());
if (server != null) {
bindMetrics(server);
}
}
private Server findServer(ApplicationContext applicationContext) {
if (applicationContext instanceof WebServerApplicationContext) {
WebServer webServer = ((WebServerApplicationContext) applicationContext).getWebServer();
if (webServer instanceof JettyWebServer) {
return ((JettyWebServer) webServer).getServer();
}
}
return null;
}
protected abstract void bindMetrics(Server server);
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2021 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.web.jetty;
import java.util.Collections;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.binder.jetty.JettyConnectionMetrics;
import org.eclipse.jetty.server.Server;
/**
* {@link AbstractJettyMetricsBinder} for {@link JettyConnectionMetrics}.
*
* @author Chris Bono
* @since 2.6.0
*/
public class JettyConnectionMetricsBinder extends AbstractJettyMetricsBinder {
private final MeterRegistry meterRegistry;
private final Iterable<Tag> tags;
public JettyConnectionMetricsBinder(MeterRegistry meterRegistry) {
this(meterRegistry, Collections.emptyList());
}
public JettyConnectionMetricsBinder(MeterRegistry meterRegistry, Iterable<Tag> tags) {
this.meterRegistry = meterRegistry;
this.tags = tags;
}
@Override
protected void bindMetrics(Server server) {
JettyConnectionMetrics.addToAllConnectors(server, this.meterRegistry, this.tags);
}
}

View File

@@ -21,23 +21,16 @@ import java.util.Collections;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.binder.jetty.JettyServerThreadPoolMetrics;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.boot.web.context.WebServerApplicationContext;
import org.springframework.boot.web.embedded.jetty.JettyWebServer;
import org.springframework.boot.web.server.WebServer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
/**
* Binds {@link JettyServerThreadPoolMetrics} in response to the
* {@link ApplicationStartedEvent}.
* {@link AbstractJettyMetricsBinder} for {@link JettyServerThreadPoolMetrics}.
*
* @author Andy Wilkinson
* @since 2.1.0
*/
public class JettyServerThreadPoolMetricsBinder implements ApplicationListener<ApplicationStartedEvent> {
public class JettyServerThreadPoolMetricsBinder extends AbstractJettyMetricsBinder {
private final MeterRegistry meterRegistry;
@@ -53,22 +46,11 @@ public class JettyServerThreadPoolMetricsBinder implements ApplicationListener<A
}
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
ApplicationContext applicationContext = event.getApplicationContext();
ThreadPool threadPool = findThreadPool(applicationContext);
protected void bindMetrics(Server server) {
ThreadPool threadPool = server.getThreadPool();
if (threadPool != null) {
new JettyServerThreadPoolMetrics(threadPool, this.tags).bindTo(this.meterRegistry);
}
}
private ThreadPool findThreadPool(ApplicationContext applicationContext) {
if (applicationContext instanceof WebServerApplicationContext) {
WebServer webServer = ((WebServerApplicationContext) applicationContext).getWebServer();
if (webServer instanceof JettyWebServer) {
return ((JettyWebServer) webServer).getServer().getThreadPool();
}
}
return null;
}
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright 2012-2021 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.web.jetty;
import java.util.Collections;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.binder.jetty.JettySslHandshakeMetrics;
import org.eclipse.jetty.server.Server;
/**
* {@link AbstractJettyMetricsBinder} for {@link JettySslHandshakeMetrics}.
*
* @author Chris Bono
* @since 2.6.0
*/
public class JettySslHandshakeMetricsBinder extends AbstractJettyMetricsBinder {
private final MeterRegistry meterRegistry;
private final Iterable<Tag> tags;
public JettySslHandshakeMetricsBinder(MeterRegistry meterRegistry) {
this(meterRegistry, Collections.emptyList());
}
public JettySslHandshakeMetricsBinder(MeterRegistry meterRegistry, Iterable<Tag> tags) {
this.meterRegistry = meterRegistry;
this.tags = tags;
}
@Override
protected void bindMetrics(Server server) {
JettySslHandshakeMetrics.addToAllConnectors(server, this.meterRegistry, this.tags);
}
}

View File

@@ -954,6 +954,7 @@ To disable the auto-configured connection pool metrics, set the following proper
[[actuator.metrics.supported.jetty]]
==== Jetty Metrics
Auto-configuration will bind metrics for Jetty's `ThreadPool` using Micrometer's `JettyServerThreadPoolMetrics`.
Metrics for Jetty's `Connector`s are bound using Micrometer's `JettyConnectionMetrics` and, in addition when configprop:server.ssl.enabled[] is set to `true`, Micrometer's `JettySslHandshakeMetrics`.