Skip to content

Commit c62a681

Browse files
committed
Polish "Add startup time metrics"
See gh-27878
1 parent 2e67963 commit c62a681

File tree

19 files changed

+201
-185
lines changed

19 files changed

+201
-185
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/startup/StartupTimeMetricsAutoConfiguration.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import org.springframework.context.annotation.Configuration;
3131

3232
/**
33-
* {@link EnableAutoConfiguration Auto-configuration} for the {@link StartupTimeMetrics}.
33+
* {@link EnableAutoConfiguration Auto-configuration} for startup time metrics.
3434
*
3535
* @author Chris Bono
3636
* @since 2.6.0
@@ -43,7 +43,7 @@ public class StartupTimeMetricsAutoConfiguration {
4343

4444
@Bean
4545
@ConditionalOnMissingBean
46-
StartupTimeMetrics startupTimeMetrics(MeterRegistry meterRegistry) {
46+
public StartupTimeMetrics startupTimeMetrics(MeterRegistry meterRegistry) {
4747
return new StartupTimeMetrics(meterRegistry);
4848
}
4949

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/startup/StartupTimeMetricsAutoConfigurationTests.java

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
package org.springframework.boot.actuate.autoconfigure.metrics.startup;
1818

1919
import java.time.Duration;
20+
import java.util.concurrent.TimeUnit;
2021

21-
import io.micrometer.core.instrument.Tags;
22+
import io.micrometer.core.instrument.TimeGauge;
2223
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
2324
import org.junit.jupiter.api.Test;
2425

@@ -29,15 +30,15 @@
2930
import org.springframework.boot.context.event.ApplicationReadyEvent;
3031
import org.springframework.boot.context.event.ApplicationStartedEvent;
3132
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
32-
import org.springframework.context.annotation.Bean;
33-
import org.springframework.context.annotation.Configuration;
3433

3534
import static org.assertj.core.api.Assertions.assertThat;
35+
import static org.mockito.Mockito.mock;
3636

3737
/**
3838
* Tests for {@link StartupTimeMetricsAutoConfiguration}.
3939
*
4040
* @author Chris Bono
41+
* @author Stephane Nicoll
4142
*/
4243
class StartupTimeMetricsAutoConfigurationTests {
4344

@@ -47,14 +48,18 @@ class StartupTimeMetricsAutoConfigurationTests {
4748
@Test
4849
void startupTimeMetricsAreRecorded() {
4950
this.contextRunner.run((context) -> {
50-
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
51-
context.getSourceApplicationContext(), Duration.ofMillis(2500)));
52-
context.publishEvent(new ApplicationReadyEvent(new SpringApplication(), null,
53-
context.getSourceApplicationContext(), Duration.ofMillis(3000)));
5451
assertThat(context).hasSingleBean(StartupTimeMetrics.class);
5552
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
56-
assertThat(registry.find("application.started.time").timeGauge()).isNotNull();
57-
assertThat(registry.find("application.ready.time").timeGauge()).isNotNull();
53+
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
54+
context.getSourceApplicationContext(), Duration.ofMillis(1500)));
55+
TimeGauge startedTimeGage = registry.find("application.started.time").timeGauge();
56+
assertThat(startedTimeGage).isNotNull();
57+
assertThat(startedTimeGage.value(TimeUnit.MILLISECONDS)).isEqualTo(1500L);
58+
context.publishEvent(new ApplicationReadyEvent(new SpringApplication(), null,
59+
context.getSourceApplicationContext(), Duration.ofMillis(2000)));
60+
TimeGauge readyTimeGage = registry.find("application.ready.time").timeGauge();
61+
assertThat(readyTimeGage).isNotNull();
62+
assertThat(readyTimeGage.value(TimeUnit.MILLISECONDS)).isEqualTo(2000L);
5863
});
5964
}
6065

@@ -74,19 +79,10 @@ void startupTimeMetricsCanBeDisabled() {
7479

7580
@Test
7681
void customStartupTimeMetricsAreRespected() {
77-
this.contextRunner.withUserConfiguration(CustomStartupTimeMetricsConfiguration.class)
82+
this.contextRunner
83+
.withBean("customStartupTimeMetrics", StartupTimeMetrics.class, () -> mock(StartupTimeMetrics.class))
7884
.run((context) -> assertThat(context).hasSingleBean(StartupTimeMetrics.class)
79-
.hasBean("customStartTimeMetrics"));
80-
}
81-
82-
@Configuration(proxyBeanMethods = false)
83-
static class CustomStartupTimeMetricsConfiguration {
84-
85-
@Bean
86-
StartupTimeMetrics customStartTimeMetrics() {
87-
return new StartupTimeMetrics(new SimpleMeterRegistry(), Tags.empty(), "myapp.started", "myapp.ready");
88-
}
89-
85+
.hasBean("customStartupTimeMetrics"));
9086
}
9187

9288
}

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

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.metrics.web.jetty;
1818

19-
import java.time.Duration;
20-
2119
import io.micrometer.core.instrument.MeterRegistry;
2220
import io.micrometer.core.instrument.Tags;
2321
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
@@ -37,6 +35,7 @@
3735
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
3836
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
3937
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
38+
import org.springframework.context.ConfigurableApplicationContext;
4039
import org.springframework.context.annotation.Bean;
4140
import org.springframework.context.annotation.Configuration;
4241
import org.springframework.http.server.reactive.HttpHandler;
@@ -59,8 +58,7 @@ void autoConfiguresThreadPoolMetricsWithEmbeddedServletJetty() {
5958
ServletWebServerFactoryAutoConfiguration.class))
6059
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
6160
.run((context) -> {
62-
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
63-
context.getSourceApplicationContext(), Duration.ZERO));
61+
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
6462
assertThat(context).hasSingleBean(JettyServerThreadPoolMetricsBinder.class);
6563
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
6664
assertThat(registry.find("jetty.threads.config.min").meter()).isNotNull();
@@ -74,8 +72,7 @@ void autoConfiguresThreadPoolMetricsWithEmbeddedReactiveJetty() {
7472
ReactiveWebServerFactoryAutoConfiguration.class))
7573
.withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class)
7674
.run((context) -> {
77-
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
78-
context.getSourceApplicationContext(), Duration.ZERO));
75+
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
7976
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
8077
assertThat(registry.find("jetty.threads.config.min").meter()).isNotNull();
8178
});
@@ -96,8 +93,7 @@ void autoConfiguresConnectionMetricsWithEmbeddedServletJetty() {
9693
ServletWebServerFactoryAutoConfiguration.class))
9794
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
9895
.run((context) -> {
99-
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
100-
context.getSourceApplicationContext(), Duration.ZERO));
96+
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
10197
assertThat(context).hasSingleBean(JettyConnectionMetricsBinder.class);
10298
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
10399
assertThat(registry.find("jetty.connections.messages.in").meter()).isNotNull();
@@ -111,8 +107,7 @@ void autoConfiguresConnectionMetricsWithEmbeddedReactiveJetty() {
111107
ReactiveWebServerFactoryAutoConfiguration.class))
112108
.withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class)
113109
.run((context) -> {
114-
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
115-
context.getSourceApplicationContext(), Duration.ZERO));
110+
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
116111
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
117112
assertThat(registry.find("jetty.connections.messages.in").meter()).isNotNull();
118113
});
@@ -126,8 +121,7 @@ void allowsCustomJettyConnectionMetricsBinderToBeUsed() {
126121
.withUserConfiguration(ServletWebServerConfiguration.class, CustomJettyConnectionMetricsBinder.class,
127122
MeterRegistryConfiguration.class)
128123
.run((context) -> {
129-
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
130-
context.getSourceApplicationContext(), Duration.ZERO));
124+
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
131125
assertThat(context).hasSingleBean(JettyConnectionMetricsBinder.class)
132126
.hasBean("customJettyConnectionMetricsBinder");
133127
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
@@ -145,8 +139,7 @@ void autoConfiguresSslHandshakeMetricsWithEmbeddedServletJetty() {
145139
.withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks",
146140
"server.ssl.key-store-password: secret", "server.ssl.key-password: password")
147141
.run((context) -> {
148-
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
149-
context.getSourceApplicationContext(), Duration.ZERO));
142+
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
150143
assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class);
151144
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
152145
assertThat(registry.find("jetty.ssl.handshakes").meter()).isNotNull();
@@ -162,8 +155,7 @@ void autoConfiguresSslHandshakeMetricsWithEmbeddedReactiveJetty() {
162155
.withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks",
163156
"server.ssl.key-store-password: secret", "server.ssl.key-password: password")
164157
.run((context) -> {
165-
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
166-
context.getSourceApplicationContext(), Duration.ZERO));
158+
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
167159
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
168160
assertThat(registry.find("jetty.ssl.handshakes").meter()).isNotNull();
169161
});
@@ -179,8 +171,7 @@ void allowsCustomJettySslHandshakeMetricsBinderToBeUsed() {
179171
.withPropertyValues("server.ssl.enabled: true", "server.ssl.key-store: src/test/resources/test.jks",
180172
"server.ssl.key-store-password: secret", "server.ssl.key-password: password")
181173
.run((context) -> {
182-
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
183-
context.getSourceApplicationContext(), Duration.ZERO));
174+
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
184175
assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class)
185176
.hasBean("customJettySslHandshakeMetricsBinder");
186177
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
@@ -215,6 +206,10 @@ void doesNotAutoConfigureSslHandshakeMetricsWhenSslEnabledPropertySetToFalse() {
215206
.run((context) -> assertThat(context).doesNotHaveBean(JettySslHandshakeMetricsBinder.class));
216207
}
217208

209+
private ApplicationStartedEvent createApplicationStartedEvent(ConfigurableApplicationContext context) {
210+
return new ApplicationStartedEvent(new SpringApplication(), null, context, null);
211+
}
212+
218213
@Configuration(proxyBeanMethods = false)
219214
static class MeterRegistryConfiguration {
220215

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat;
1818

19-
import java.time.Duration;
2019
import java.util.Collections;
2120
import java.util.concurrent.atomic.AtomicInteger;
2221

@@ -39,6 +38,7 @@
3938
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
4039
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
4140
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
41+
import org.springframework.context.ConfigurableApplicationContext;
4242
import org.springframework.context.annotation.Bean;
4343
import org.springframework.context.annotation.Configuration;
4444
import org.springframework.http.server.reactive.HttpHandler;
@@ -62,8 +62,7 @@ void autoConfiguresTomcatMetricsWithEmbeddedServletTomcat() {
6262
ServletWebServerFactoryAutoConfiguration.class))
6363
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
6464
.withPropertyValues("server.tomcat.mbeanregistry.enabled=true").run((context) -> {
65-
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
66-
context.getSourceApplicationContext(), Duration.ZERO));
65+
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
6766
assertThat(context).hasSingleBean(TomcatMetricsBinder.class);
6867
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
6968
assertThat(registry.find("tomcat.sessions.active.max").meter()).isNotNull();
@@ -79,8 +78,7 @@ void autoConfiguresTomcatMetricsWithEmbeddedReactiveTomcat() {
7978
ReactiveWebServerFactoryAutoConfiguration.class))
8079
.withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class)
8180
.withPropertyValues("server.tomcat.mbeanregistry.enabled=true").run((context) -> {
82-
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
83-
context.getSourceApplicationContext(), Duration.ZERO));
81+
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
8482
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
8583
assertThat(registry.find("tomcat.sessions.active.max").meter()).isNotNull();
8684
assertThat(registry.find("tomcat.threads.current").meter()).isNotNull();
@@ -110,6 +108,10 @@ void allowsCustomTomcatMetricsToBeUsed() {
110108
.hasBean("customTomcatMetrics"));
111109
}
112110

111+
private ApplicationStartedEvent createApplicationStartedEvent(ConfigurableApplicationContext context) {
112+
return new ApplicationStartedEvent(new SpringApplication(), null, context, null);
113+
}
114+
113115
private void resetTomcatState() {
114116
ReflectionTestUtils.setField(Registry.class, "registry", null);
115117
AtomicInteger containerCounter = (AtomicInteger) ReflectionTestUtils.getField(TomcatWebServer.class,

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/startup/StartupTimeMetrics.java

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.actuate.metrics.startup;
1818

19+
import java.time.Duration;
1920
import java.util.Collections;
2021
import java.util.concurrent.TimeUnit;
2122

@@ -39,6 +40,16 @@
3940
*/
4041
public class StartupTimeMetrics implements SmartApplicationListener {
4142

43+
/**
44+
* The default name to use for the application started time metric.
45+
*/
46+
public static final String APPLICATION_STARTED_TIME_METRIC_NAME = "application.started.time";
47+
48+
/**
49+
* The default name to use for the application ready time metric.
50+
*/
51+
public static final String APPLICATION_READY_TIME_METRIC_NAME = "application.ready.time";
52+
4253
private final MeterRegistry meterRegistry;
4354

4455
private final String applicationStartedTimeMetricName;
@@ -47,10 +58,26 @@ public class StartupTimeMetrics implements SmartApplicationListener {
4758

4859
private final Iterable<Tag> tags;
4960

61+
/**
62+
* Create a new instance using default metric names.
63+
* @param meterRegistry the registry to use
64+
* @see #APPLICATION_STARTED_TIME_METRIC_NAME
65+
* @see #APPLICATION_READY_TIME_METRIC_NAME
66+
*/
5067
public StartupTimeMetrics(MeterRegistry meterRegistry) {
51-
this(meterRegistry, Collections.emptyList(), "application.started.time", "application.ready.time");
68+
this(meterRegistry, Collections.emptyList(), APPLICATION_STARTED_TIME_METRIC_NAME,
69+
APPLICATION_READY_TIME_METRIC_NAME);
5270
}
5371

72+
/**
73+
* Create a new instance using the specified options.
74+
* @param meterRegistry the registry to use
75+
* @param tags the tags to associate to application startup metrics
76+
* @param applicationStartedTimeMetricName the name to use for the application started
77+
* time metric
78+
* @param applicationReadyTimeMetricName the name to use for the application ready
79+
* time metric
80+
*/
5481
public StartupTimeMetrics(MeterRegistry meterRegistry, Iterable<Tag> tags, String applicationStartedTimeMetricName,
5582
String applicationReadyTimeMetricName) {
5683
this.meterRegistry = meterRegistry;
@@ -76,29 +103,28 @@ public void onApplicationEvent(ApplicationEvent event) {
76103
}
77104

78105
private void onApplicationStarted(ApplicationStartedEvent event) {
79-
if (event.getStartupTime() == null) {
106+
if (event.getStartedTime() == null) {
80107
return;
81108
}
82-
TimeGauge
83-
.builder(this.applicationStartedTimeMetricName, () -> event.getStartupTime().toMillis(),
84-
TimeUnit.MILLISECONDS)
85-
.tags(maybeDcorateTagsWithApplicationInfo(event.getSpringApplication()))
86-
.description("Time taken (ms) to start the application").register(this.meterRegistry);
109+
registerGauge(this.applicationStartedTimeMetricName, "Time taken (ms) to start the application",
110+
event.getStartedTime(), createTagsFrom(event.getSpringApplication()));
87111
}
88112

89113
private void onApplicationReady(ApplicationReadyEvent event) {
90-
if (event.getStartupTime() == null) {
114+
if (event.getReadyTime() == null) {
91115
return;
92116
}
93-
TimeGauge
94-
.builder(this.applicationReadyTimeMetricName, () -> event.getStartupTime().toMillis(),
95-
TimeUnit.MILLISECONDS)
96-
.tags(maybeDcorateTagsWithApplicationInfo(event.getSpringApplication()))
97-
.description("Time taken (ms) for the application to be ready to serve requests")
117+
registerGauge(this.applicationReadyTimeMetricName,
118+
"Time taken (ms) for the application to be ready to serve requests", event.getReadyTime(),
119+
createTagsFrom(event.getSpringApplication()));
120+
}
121+
122+
private void registerGauge(String metricName, String description, Duration time, Iterable<Tag> tags) {
123+
TimeGauge.builder(metricName, time::toMillis, TimeUnit.MILLISECONDS).tags(tags).description(description)
98124
.register(this.meterRegistry);
99125
}
100126

101-
private Iterable<Tag> maybeDcorateTagsWithApplicationInfo(SpringApplication springApplication) {
127+
private Iterable<Tag> createTagsFrom(SpringApplication springApplication) {
102128
Class<?> mainClass = springApplication.getMainApplicationClass();
103129
if (mainClass == null) {
104130
return this.tags;

0 commit comments

Comments
 (0)