Polish "Add startup time metrics"

See gh-27878
pull/28064/head
Stephane Nicoll 3 years ago
parent 2e67963bfe
commit c62a6819fe

@ -30,7 +30,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* {@link EnableAutoConfiguration Auto-configuration} for the {@link StartupTimeMetrics}.
* {@link EnableAutoConfiguration Auto-configuration} for startup time metrics.
*
* @author Chris Bono
* @since 2.6.0
@ -43,7 +43,7 @@ public class StartupTimeMetricsAutoConfiguration {
@Bean
@ConditionalOnMissingBean
StartupTimeMetrics startupTimeMetrics(MeterRegistry meterRegistry) {
public StartupTimeMetrics startupTimeMetrics(MeterRegistry meterRegistry) {
return new StartupTimeMetrics(meterRegistry);
}

@ -17,8 +17,9 @@
package org.springframework.boot.actuate.autoconfigure.metrics.startup;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.TimeGauge;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
@ -29,15 +30,15 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link StartupTimeMetricsAutoConfiguration}.
*
* @author Chris Bono
* @author Stephane Nicoll
*/
class StartupTimeMetricsAutoConfigurationTests {
@ -47,14 +48,18 @@ class StartupTimeMetricsAutoConfigurationTests {
@Test
void startupTimeMetricsAreRecorded() {
this.contextRunner.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext(), Duration.ofMillis(2500)));
context.publishEvent(new ApplicationReadyEvent(new SpringApplication(), null,
context.getSourceApplicationContext(), Duration.ofMillis(3000)));
assertThat(context).hasSingleBean(StartupTimeMetrics.class);
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("application.started.time").timeGauge()).isNotNull();
assertThat(registry.find("application.ready.time").timeGauge()).isNotNull();
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext(), Duration.ofMillis(1500)));
TimeGauge startedTimeGage = registry.find("application.started.time").timeGauge();
assertThat(startedTimeGage).isNotNull();
assertThat(startedTimeGage.value(TimeUnit.MILLISECONDS)).isEqualTo(1500L);
context.publishEvent(new ApplicationReadyEvent(new SpringApplication(), null,
context.getSourceApplicationContext(), Duration.ofMillis(2000)));
TimeGauge readyTimeGage = registry.find("application.ready.time").timeGauge();
assertThat(readyTimeGage).isNotNull();
assertThat(readyTimeGage.value(TimeUnit.MILLISECONDS)).isEqualTo(2000L);
});
}
@ -74,19 +79,10 @@ class StartupTimeMetricsAutoConfigurationTests {
@Test
void customStartupTimeMetricsAreRespected() {
this.contextRunner.withUserConfiguration(CustomStartupTimeMetricsConfiguration.class)
this.contextRunner
.withBean("customStartupTimeMetrics", StartupTimeMetrics.class, () -> mock(StartupTimeMetrics.class))
.run((context) -> assertThat(context).hasSingleBean(StartupTimeMetrics.class)
.hasBean("customStartTimeMetrics"));
}
@Configuration(proxyBeanMethods = false)
static class CustomStartupTimeMetricsConfiguration {
@Bean
StartupTimeMetrics customStartTimeMetrics() {
return new StartupTimeMetrics(new SimpleMeterRegistry(), Tags.empty(), "myapp.started", "myapp.ready");
}
.hasBean("customStartupTimeMetrics"));
}
}

@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.metrics.web.jetty;
import java.time.Duration;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
@ -37,6 +35,7 @@ import org.springframework.boot.web.embedded.jetty.JettyReactiveWebServerFactory
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
@ -59,8 +58,7 @@ class JettyMetricsAutoConfigurationTests {
ServletWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext(), Duration.ZERO));
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
assertThat(context).hasSingleBean(JettyServerThreadPoolMetricsBinder.class);
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.threads.config.min").meter()).isNotNull();
@ -74,8 +72,7 @@ class JettyMetricsAutoConfigurationTests {
ReactiveWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class)
.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext(), Duration.ZERO));
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.threads.config.min").meter()).isNotNull();
});
@ -96,8 +93,7 @@ class JettyMetricsAutoConfigurationTests {
ServletWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext(), Duration.ZERO));
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
assertThat(context).hasSingleBean(JettyConnectionMetricsBinder.class);
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.connections.messages.in").meter()).isNotNull();
@ -111,8 +107,7 @@ class JettyMetricsAutoConfigurationTests {
ReactiveWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class)
.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext(), Duration.ZERO));
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.connections.messages.in").meter()).isNotNull();
});
@ -126,8 +121,7 @@ class JettyMetricsAutoConfigurationTests {
.withUserConfiguration(ServletWebServerConfiguration.class, CustomJettyConnectionMetricsBinder.class,
MeterRegistryConfiguration.class)
.run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext(), Duration.ZERO));
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
assertThat(context).hasSingleBean(JettyConnectionMetricsBinder.class)
.hasBean("customJettyConnectionMetricsBinder");
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
@ -145,8 +139,7 @@ class JettyMetricsAutoConfigurationTests {
.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(), Duration.ZERO));
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class);
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.ssl.handshakes").meter()).isNotNull();
@ -162,8 +155,7 @@ class JettyMetricsAutoConfigurationTests {
.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(), Duration.ZERO));
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("jetty.ssl.handshakes").meter()).isNotNull();
});
@ -179,8 +171,7 @@ class JettyMetricsAutoConfigurationTests {
.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(), Duration.ZERO));
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
assertThat(context).hasSingleBean(JettySslHandshakeMetricsBinder.class)
.hasBean("customJettySslHandshakeMetricsBinder");
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
@ -215,6 +206,10 @@ class JettyMetricsAutoConfigurationTests {
.run((context) -> assertThat(context).doesNotHaveBean(JettySslHandshakeMetricsBinder.class));
}
private ApplicationStartedEvent createApplicationStartedEvent(ConfigurableApplicationContext context) {
return new ApplicationStartedEvent(new SpringApplication(), null, context, null);
}
@Configuration(proxyBeanMethods = false)
static class MeterRegistryConfiguration {

@ -16,7 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat;
import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
@ -39,6 +38,7 @@ import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactor
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.reactive.HttpHandler;
@ -62,8 +62,7 @@ class TomcatMetricsAutoConfigurationTests {
ServletWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ServletWebServerConfiguration.class, MeterRegistryConfiguration.class)
.withPropertyValues("server.tomcat.mbeanregistry.enabled=true").run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext(), Duration.ZERO));
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
assertThat(context).hasSingleBean(TomcatMetricsBinder.class);
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("tomcat.sessions.active.max").meter()).isNotNull();
@ -79,8 +78,7 @@ class TomcatMetricsAutoConfigurationTests {
ReactiveWebServerFactoryAutoConfiguration.class))
.withUserConfiguration(ReactiveWebServerConfiguration.class, MeterRegistryConfiguration.class)
.withPropertyValues("server.tomcat.mbeanregistry.enabled=true").run((context) -> {
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
context.getSourceApplicationContext(), Duration.ZERO));
context.publishEvent(createApplicationStartedEvent(context.getSourceApplicationContext()));
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
assertThat(registry.find("tomcat.sessions.active.max").meter()).isNotNull();
assertThat(registry.find("tomcat.threads.current").meter()).isNotNull();
@ -110,6 +108,10 @@ class TomcatMetricsAutoConfigurationTests {
.hasBean("customTomcatMetrics"));
}
private ApplicationStartedEvent createApplicationStartedEvent(ConfigurableApplicationContext context) {
return new ApplicationStartedEvent(new SpringApplication(), null, context, null);
}
private void resetTomcatState() {
ReflectionTestUtils.setField(Registry.class, "registry", null);
AtomicInteger containerCounter = (AtomicInteger) ReflectionTestUtils.getField(TomcatWebServer.class,

@ -16,6 +16,7 @@
package org.springframework.boot.actuate.metrics.startup;
import java.time.Duration;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
@ -39,6 +40,16 @@ import org.springframework.context.event.SmartApplicationListener;
*/
public class StartupTimeMetrics implements SmartApplicationListener {
/**
* The default name to use for the application started time metric.
*/
public static final String APPLICATION_STARTED_TIME_METRIC_NAME = "application.started.time";
/**
* The default name to use for the application ready time metric.
*/
public static final String APPLICATION_READY_TIME_METRIC_NAME = "application.ready.time";
private final MeterRegistry meterRegistry;
private final String applicationStartedTimeMetricName;
@ -47,10 +58,26 @@ public class StartupTimeMetrics implements SmartApplicationListener {
private final Iterable<Tag> tags;
/**
* Create a new instance using default metric names.
* @param meterRegistry the registry to use
* @see #APPLICATION_STARTED_TIME_METRIC_NAME
* @see #APPLICATION_READY_TIME_METRIC_NAME
*/
public StartupTimeMetrics(MeterRegistry meterRegistry) {
this(meterRegistry, Collections.emptyList(), "application.started.time", "application.ready.time");
this(meterRegistry, Collections.emptyList(), APPLICATION_STARTED_TIME_METRIC_NAME,
APPLICATION_READY_TIME_METRIC_NAME);
}
/**
* Create a new instance using the specified options.
* @param meterRegistry the registry to use
* @param tags the tags to associate to application startup metrics
* @param applicationStartedTimeMetricName the name to use for the application started
* time metric
* @param applicationReadyTimeMetricName the name to use for the application ready
* time metric
*/
public StartupTimeMetrics(MeterRegistry meterRegistry, Iterable<Tag> tags, String applicationStartedTimeMetricName,
String applicationReadyTimeMetricName) {
this.meterRegistry = meterRegistry;
@ -76,29 +103,28 @@ public class StartupTimeMetrics implements SmartApplicationListener {
}
private void onApplicationStarted(ApplicationStartedEvent event) {
if (event.getStartupTime() == null) {
if (event.getStartedTime() == null) {
return;
}
TimeGauge
.builder(this.applicationStartedTimeMetricName, () -> event.getStartupTime().toMillis(),
TimeUnit.MILLISECONDS)
.tags(maybeDcorateTagsWithApplicationInfo(event.getSpringApplication()))
.description("Time taken (ms) to start the application").register(this.meterRegistry);
registerGauge(this.applicationStartedTimeMetricName, "Time taken (ms) to start the application",
event.getStartedTime(), createTagsFrom(event.getSpringApplication()));
}
private void onApplicationReady(ApplicationReadyEvent event) {
if (event.getStartupTime() == null) {
if (event.getReadyTime() == null) {
return;
}
TimeGauge
.builder(this.applicationReadyTimeMetricName, () -> event.getStartupTime().toMillis(),
TimeUnit.MILLISECONDS)
.tags(maybeDcorateTagsWithApplicationInfo(event.getSpringApplication()))
.description("Time taken (ms) for the application to be ready to serve requests")
registerGauge(this.applicationReadyTimeMetricName,
"Time taken (ms) for the application to be ready to serve requests", event.getReadyTime(),
createTagsFrom(event.getSpringApplication()));
}
private void registerGauge(String metricName, String description, Duration time, Iterable<Tag> tags) {
TimeGauge.builder(metricName, time::toMillis, TimeUnit.MILLISECONDS).tags(tags).description(description)
.register(this.meterRegistry);
}
private Iterable<Tag> maybeDcorateTagsWithApplicationInfo(SpringApplication springApplication) {
private Iterable<Tag> createTagsFrom(SpringApplication springApplication) {
Class<?> mainClass = springApplication.getMainApplicationClass();
if (mainClass == null) {
return this.tags;

@ -21,6 +21,7 @@ import java.util.concurrent.TimeUnit;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.TimeGauge;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -40,44 +41,52 @@ import static org.mockito.Mockito.mock;
*/
class StartupTimeMetricsTests {
private static final long APP_STARTED_TIME_MS = 2500;
private static final long APP_RUNNING_TIME_MS = 2900;
private MeterRegistry registry;
private StartupTimeMetrics metrics;
@BeforeEach
void prepareUnit() {
void setup() {
this.registry = new SimpleMeterRegistry();
this.metrics = new StartupTimeMetrics(this.registry);
}
@Test
void metricsRecordedWithoutCustomTags() {
this.metrics.onApplicationEvent(applicationStartedEvent(APP_STARTED_TIME_MS));
this.metrics.onApplicationEvent(applicationReadyEvent(APP_RUNNING_TIME_MS));
assertMetricExistsWithValue("application.started.time", APP_STARTED_TIME_MS);
assertMetricExistsWithValue("application.ready.time", APP_RUNNING_TIME_MS);
this.metrics.onApplicationEvent(applicationStartedEvent(2000L));
this.metrics.onApplicationEvent(applicationReadyEvent(2200L));
assertMetricExistsWithValue("application.started.time", 2000L);
assertMetricExistsWithValue("application.ready.time", 2200L);
}
@Test
void metricsRecordedWithCustomTagsAndMetricNames() {
Tags tags = Tags.of("foo", "bar");
this.metrics = new StartupTimeMetrics(this.registry, tags, "m1", "m2");
this.metrics.onApplicationEvent(applicationStartedEvent(APP_STARTED_TIME_MS));
this.metrics.onApplicationEvent(applicationReadyEvent(APP_RUNNING_TIME_MS));
assertMetricExistsWithCustomTagsAndValue("m1", tags, APP_STARTED_TIME_MS);
assertMetricExistsWithCustomTagsAndValue("m2", tags, APP_RUNNING_TIME_MS);
this.metrics.onApplicationEvent(applicationStartedEvent(1000L));
this.metrics.onApplicationEvent(applicationReadyEvent(1050L));
assertMetricExistsWithCustomTagsAndValue("m1", tags, 1000L);
assertMetricExistsWithCustomTagsAndValue("m2", tags, 1050L);
}
@Test
void metricRecordedWithoutMainAppClassTag() {
SpringApplication application = mock(SpringApplication.class);
this.metrics.onApplicationEvent(new ApplicationStartedEvent(application, null, null, Duration.ofSeconds(2)));
TimeGauge applicationStartedGague = this.registry.find("application.started.time").timeGauge();
assertThat(applicationStartedGague).isNotNull();
assertThat(applicationStartedGague.getId().getTags()).isEmpty();
}
@Test
void metricsRecordedWithoutMainAppClassTagWhenMainAppClassNotAvailable() {
this.metrics.onApplicationEvent(applicationStartedEvent(APP_STARTED_TIME_MS));
this.metrics.onApplicationEvent(applicationReadyEvent(APP_RUNNING_TIME_MS));
assertThat(this.registry.find("application.started.time").timeGauge()).isNotNull();
assertThat(this.registry.find("application.ready.time").timeGauge()).isNotNull();
void metricRecordedWithoutMainAppClassTagAndAdditionalTags() {
SpringApplication application = mock(SpringApplication.class);
Tags tags = Tags.of("foo", "bar");
this.metrics = new StartupTimeMetrics(this.registry, tags, "started", "ready");
this.metrics.onApplicationEvent(new ApplicationReadyEvent(application, null, null, Duration.ofSeconds(2)));
TimeGauge applicationReadyGague = this.registry.find("ready").timeGauge();
assertThat(applicationReadyGague).isNotNull();
assertThat(applicationReadyGague.getId().getTags()).containsExactlyElementsOf(tags);
}
@Test
@ -102,16 +111,16 @@ class StartupTimeMetricsTests {
(startupTimeMs != null) ? Duration.ofMillis(startupTimeMs) : null);
}
private void assertMetricExistsWithValue(String metricName, double expectedValueInMillis) {
private void assertMetricExistsWithValue(String metricName, long expectedValueInMillis) {
assertMetricExistsWithCustomTagsAndValue(metricName, Tags.empty(), expectedValueInMillis);
}
private void assertMetricExistsWithCustomTagsAndValue(String metricName, Tags expectedCustomTags,
double expectedValueInMillis) {
Long expectedValueInMillis) {
assertThat(this.registry.find(metricName)
.tags(Tags.concat(expectedCustomTags, "main-application-class", TestMainApplication.class.getName()))
.timeGauge()).isNotNull().extracting((m) -> m.value(TimeUnit.MILLISECONDS))
.isEqualTo(expectedValueInMillis);
.isEqualTo(expectedValueInMillis.doubleValue());
}
static class TestMainApplication {

@ -16,7 +16,6 @@
package org.springframework.boot.devtools.restart;
import java.time.Duration;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
@ -111,7 +110,7 @@ class RestartApplicationListenerTests {
listener.onApplicationEvent(new ApplicationFailedEvent(application, ARGS, context, new RuntimeException()));
}
else {
listener.onApplicationEvent(new ApplicationReadyEvent(application, ARGS, context, Duration.ZERO));
listener.onApplicationEvent(new ApplicationReadyEvent(application, ARGS, context, null));
}
}

@ -656,6 +656,17 @@ The following system metrics are provided:
[[actuator.metrics.supported.application-startup]]
==== Application Startup Metrics
Auto-configuration exposes application startup time metrics:
* `application.started.time`: time taken to start the application.
* `application.ready.time`: time taken for the application to be ready to serve requests.
Metrics are tagged by the fully qualified name of the application class.
[[actuator.metrics.supported.logger]]
==== Logger Metrics
Auto-configuration enables the event metrics for both Logback and Log4J2.

@ -287,7 +287,7 @@ public class SpringApplication {
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start("applicationStarted");
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
@ -304,11 +304,12 @@ public class SpringApplication {
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
stopWatch.start("applicationReady");
Duration startedTime = Duration.ofMillis(stopWatch.getTotalTimeMillis());
stopWatch.start();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startedTime);
}
listeners.started(context, Duration.ofMillis(stopWatch.getTotalTimeMillis()));
listeners.started(context, startedTime);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {

@ -83,6 +83,7 @@ public interface SpringApplicationRunListener {
*/
@Deprecated
default void started(ConfigurableApplicationContext context) {
started(context, null);
}
/**
@ -90,11 +91,11 @@ public interface SpringApplicationRunListener {
* {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
* ApplicationRunners} have not been called.
* @param context the application context.
* @param startupTime the time taken to start the application or {@code null} if
* @param startedTime the time taken to start the application or {@code null} if
* unknown
* @since 2.0.0
* @since 2.6.0
*/
default void started(ConfigurableApplicationContext context, Duration startupTime) {
default void started(ConfigurableApplicationContext context, Duration startedTime) {
started(context);
}
@ -109,6 +110,7 @@ public interface SpringApplicationRunListener {
*/
@Deprecated
default void running(ConfigurableApplicationContext context) {
running(context, null);
}
/**
@ -116,11 +118,11 @@ public interface SpringApplicationRunListener {
* been refreshed and all {@link CommandLineRunner CommandLineRunners} and
* {@link ApplicationRunner ApplicationRunners} have been called.
* @param context the application context.
* @param startupTime the time taken for the application to be ready to service
* requests or {@code null} if unknown
* @param readyTime the time taken for the application to be ready to service requests
* or {@code null} if unknown
* @since 2.6.0
*/
default void running(ConfigurableApplicationContext context, Duration startupTime) {
default void running(ConfigurableApplicationContext context, Duration readyTime) {
running(context);
}

@ -18,6 +18,7 @@ package org.springframework.boot;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.time.Duration;
import java.util.concurrent.Callable;
import org.apache.commons.logging.Log;
@ -29,7 +30,6 @@ import org.springframework.context.ApplicationContext;
import org.springframework.core.log.LogMessage;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
/**
@ -56,9 +56,9 @@ class StartupInfoLogger {
applicationLog.debug(LogMessage.of(this::getRunningMessage));
}
void logStarted(Log applicationLog, StopWatch stopWatch) {
void logStarted(Log applicationLog, Duration startupTime) {
if (applicationLog.isInfoEnabled()) {
applicationLog.info(getStartedMessage(stopWatch));
applicationLog.info(getStartedMessage(startupTime));
}
}
@ -83,12 +83,12 @@ class StartupInfoLogger {
return message;
}
private CharSequence getStartedMessage(StopWatch stopWatch) {
private CharSequence getStartedMessage(Duration startupTime) {
StringBuilder message = new StringBuilder();
message.append("Started ");
appendApplicationName(message);
message.append(" in ");
message.append(stopWatch.getTotalTimeMillis() / 1000.0);
message.append(startupTime.toMillis() / 1000.0);
message.append(" seconds");
try {
double uptime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000.0;

@ -37,7 +37,7 @@ public class ApplicationReadyEvent extends SpringApplicationEvent {
private final ConfigurableApplicationContext context;
private final Duration startupTime;
private final Duration readyTime;
/**
* Create a new {@link ApplicationReadyEvent} instance.
@ -47,6 +47,7 @@ public class ApplicationReadyEvent extends SpringApplicationEvent {
* @deprecated since 2.6.0 for removal in 2.8.0 in favor of
* {@link #ApplicationReadyEvent(SpringApplication, String[], ConfigurableApplicationContext, Duration)}
*/
@Deprecated
public ApplicationReadyEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context) {
this(application, args, context, null);
}
@ -56,13 +57,14 @@ public class ApplicationReadyEvent extends SpringApplicationEvent {
* @param application the current application
* @param args the arguments the application is running with
* @param context the context that was being created
* @param startupTime the time taken to get the application ready to service requests
* @param readyTime the time taken to get the application ready to service requests
* @since 2.6.0
*/
public ApplicationReadyEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context,
Duration startupTime) {
Duration readyTime) {
super(application, args);
this.context = context;
this.startupTime = startupTime;
this.readyTime = readyTime;
}
/**
@ -74,11 +76,12 @@ public class ApplicationReadyEvent extends SpringApplicationEvent {
}
/**
* Return the time taken for the application to be ready to service requests.
* @return the startup time
* Return the time taken for the application to be ready to service requests, or
* {@code null} if unknown.
* @return the time taken to be ready to service requests
*/
public Duration getStartupTime() {
return this.startupTime;
public Duration getReadyTime() {
return this.readyTime;
}
}

@ -36,7 +36,7 @@ public class ApplicationStartedEvent extends SpringApplicationEvent {
private final ConfigurableApplicationContext context;
private final Duration startupTime;
private final Duration startedTime;
/**
* Create a new {@link ApplicationStartedEvent} instance.
@ -57,13 +57,14 @@ public class ApplicationStartedEvent extends SpringApplicationEvent {
* @param application the current application
* @param args the arguments the application is running with
* @param context the context that was being created
* @param startupTime the time taken to start the application
* @param startedTime the time taken to start the application
* @since 2.6.0
*/
public ApplicationStartedEvent(SpringApplication application, String[] args, ConfigurableApplicationContext context,
Duration startupTime) {
Duration startedTime) {
super(application, args);
this.context = context;
this.startupTime = startupTime;
this.startedTime = startedTime;
}
/**
@ -75,11 +76,11 @@ public class ApplicationStartedEvent extends SpringApplicationEvent {
}
/**
* Return the time taken to start the application.
* Return the time taken to start the application, or {@code null} if unknown.
* @return the startup time
*/
public Duration getStartupTime() {
return this.startupTime;
public Duration getStartedTime() {
return this.startedTime;
}
}

@ -104,26 +104,14 @@ public class EventPublishingRunListener implements SpringApplicationRunListener,
}
@Override
@Deprecated
public void started(ConfigurableApplicationContext context) {
started(context, null);
}
@Override
public void started(ConfigurableApplicationContext context, Duration startupTime) {
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, startupTime));
public void started(ConfigurableApplicationContext context, Duration startedTime) {
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, startedTime));
AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
}
@Override
@Deprecated
public void running(ConfigurableApplicationContext context) {
running(context, null);
}
@Override
public void running(ConfigurableApplicationContext context, Duration startupTime) {
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, startupTime));
public void running(ConfigurableApplicationContext context, Duration readyTime) {
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, readyTime));
AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
}

@ -88,6 +88,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.event.SmartApplicationListener;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.core.Ordered;
@ -362,36 +363,18 @@ class SpringApplicationTests {
void applicationRunningEventListener() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebApplicationType(WebApplicationType.NONE);
final AtomicReference<SpringApplication> reference = new AtomicReference<>();
class ApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
reference.set(event.getSpringApplication());
}
}
application.addListeners(new ApplicationReadyEventListener());
AtomicReference<ApplicationReadyEvent> reference = setupListener(application, ApplicationReadyEvent.class);
this.context = application.run("--foo=bar");
assertThat(application).isSameAs(reference.get());
assertThat(application).isSameAs(reference.get().getSpringApplication());
}
@Test
void contextRefreshedEventListener() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebApplicationType(WebApplicationType.NONE);
final AtomicReference<ApplicationContext> reference = new AtomicReference<>();
class InitializerListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
reference.set(event.getApplicationContext());
}
}
application.setListeners(Collections.singletonList(new InitializerListener()));
AtomicReference<ContextRefreshedEvent> reference = setupListener(application, ContextRefreshedEvent.class);
this.context = application.run("--foo=bar");
assertThat(this.context).isSameAs(reference.get());
assertThat(this.context).isSameAs(reference.get().getApplicationContext());
// Custom initializers do not switch off the defaults
assertThat(getEnvironment().getProperty("foo")).isEqualTo("bar");
}
@ -419,39 +402,21 @@ class SpringApplicationTests {
}
@Test
void applicationStartedEventHasStartupTime() {
void applicationStartedEventHasStartedTime() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebApplicationType(WebApplicationType.NONE);
final AtomicReference<ApplicationStartedEvent> reference = new AtomicReference<>();
class ApplicationStartedEventListener implements ApplicationListener<ApplicationStartedEvent> {
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
reference.set(event);
}
}
application.addListeners(new ApplicationStartedEventListener());
AtomicReference<ApplicationStartedEvent> reference = setupListener(application, ApplicationStartedEvent.class);
this.context = application.run();
assertThat(reference.get()).isNotNull().extracting(ApplicationStartedEvent::getStartupTime).isNotNull();
assertThat(reference.get()).isNotNull().extracting(ApplicationStartedEvent::getStartedTime).isNotNull();
}
@Test
void applicationReadyEventHasStartupTime() {
void applicationReadyEventHasReadyTime() {
SpringApplication application = new SpringApplication(ExampleConfig.class);
application.setWebApplicationType(WebApplicationType.NONE);
final AtomicReference<ApplicationReadyEvent> reference = new AtomicReference<>();
class ApplicationReadyEventListener implements ApplicationListener<ApplicationReadyEvent> {
@Override
public void onApplicationEvent(ApplicationReadyEvent event) {
reference.set(event);
}
}
application.addListeners(new ApplicationReadyEventListener());
AtomicReference<ApplicationReadyEvent> reference = setupListener(application, ApplicationReadyEvent.class);
this.context = application.run();
assertThat(reference.get()).isNotNull().extracting(ApplicationReadyEvent::getStartupTime).isNotNull();
assertThat(reference.get()).isNotNull().extracting(ApplicationReadyEvent::getReadyTime).isNotNull();
}
@Test
@ -1287,6 +1252,27 @@ class SpringApplicationTests {
&& ((AvailabilityChangeEvent<?>) argument).getState().equals(state);
}
private <T extends ApplicationEvent> AtomicReference<T> setupListener(SpringApplication application,
Class<T> targetEventType) {
final AtomicReference<T> reference = new AtomicReference<>();
class TestEventListener implements SmartApplicationListener {
@Override
@SuppressWarnings("unchecked")
public void onApplicationEvent(ApplicationEvent event) {
reference.set((T) event);
}
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
return targetEventType.isAssignableFrom(eventType);
}
}
application.addListeners(new TestEventListener());
return reference;
}
private Condition<ConfigurableEnvironment> matchingPropertySource(final Class<?> propertySourceClass,
final String name) {

@ -18,6 +18,7 @@ package org.springframework.boot;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.Duration;
import org.apache.commons.logging.Log;
import org.junit.jupiter.api.Test;
@ -59,7 +60,7 @@ class StartupInfoLoggerTests {
stopWatch.start();
given(this.log.isInfoEnabled()).willReturn(true);
stopWatch.stop();
new StartupInfoLogger(getClass()).logStarted(this.log, stopWatch);
new StartupInfoLogger(getClass()).logStarted(this.log, Duration.ofMillis(stopWatch.getTotalTimeMillis()));
ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
verify(this.log).info(captor.capture());
assertThat(captor.getValue().toString()).matches("Started " + getClass().getSimpleName()

@ -17,7 +17,6 @@
package org.springframework.boot.admin;
import java.lang.management.ManagementFactory;
import java.time.Duration;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServer;
@ -90,10 +89,9 @@ class SpringApplicationAdminMXBeanRegistrarTests {
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
registrar.setApplicationContext(context);
registrar.onApplicationReadyEvent(new ApplicationReadyEvent(new SpringApplication(), null,
mock(ConfigurableApplicationContext.class), Duration.ZERO));
mock(ConfigurableApplicationContext.class), null));
assertThat(isApplicationReady(registrar)).isFalse();
registrar.onApplicationReadyEvent(
new ApplicationReadyEvent(new SpringApplication(), null, context, Duration.ZERO));
registrar.onApplicationReadyEvent(new ApplicationReadyEvent(new SpringApplication(), null, context, null));
assertThat(isApplicationReady(registrar)).isTrue();
}

@ -18,7 +18,6 @@ package org.springframework.boot.context;
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.function.Consumer;
import org.junit.jupiter.api.AfterEach;
@ -188,7 +187,7 @@ class ApplicationPidFileWriterTests {
ConfigurableEnvironment environment = createEnvironment(propName, propValue);
ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
given(context.getEnvironment()).willReturn(environment);
return new ApplicationReadyEvent(new SpringApplication(), new String[] {}, context, Duration.ZERO);
return new ApplicationReadyEvent(new SpringApplication(), new String[] {}, context, null);
}
private ConfigurableEnvironment createEnvironment(String propName, String propValue) {

@ -16,7 +16,6 @@
package org.springframework.boot.context.event;
import java.time.Duration;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
@ -73,9 +72,9 @@ class EventPublishingRunListenerTests {
this.runListener.contextLoaded(context);
checkApplicationEvents(ApplicationPreparedEvent.class);
context.refresh();
this.runListener.started(context, Duration.ZERO);
this.runListener.started(context, null);
checkApplicationEvents(ApplicationStartedEvent.class, AvailabilityChangeEvent.class);
this.runListener.running(context, Duration.ZERO);
this.runListener.running(context, null);
checkApplicationEvents(ApplicationReadyEvent.class, AvailabilityChangeEvent.class);
}

Loading…
Cancel
Save