Skip to content

Commit 98a0e07

Browse files
committed
Polish "Add startup time metrics"
See gh-27878
1 parent c9dc40a commit 98a0e07

File tree

15 files changed

+143
-147
lines changed

15 files changed

+143
-147
lines changed
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
2222
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
23-
import org.springframework.boot.actuate.metrics.startup.StartupTimeMetrics;
23+
import org.springframework.boot.actuate.metrics.startup.StartupTimeMetricsListener;
2424
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
2525
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2626
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -39,12 +39,12 @@
3939
@AutoConfigureAfter({ MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class })
4040
@ConditionalOnClass(MeterRegistry.class)
4141
@ConditionalOnBean(MeterRegistry.class)
42-
public class StartupTimeMetricsAutoConfiguration {
42+
public class StartupTimeMetricsListenerAutoConfiguration {
4343

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

5050
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ org.springframework.boot.actuate.autoconfigure.metrics.mongo.MongoMetricsAutoCon
7575
org.springframework.boot.actuate.autoconfigure.metrics.orm.jpa.HibernateMetricsAutoConfiguration,\
7676
org.springframework.boot.actuate.autoconfigure.metrics.r2dbc.ConnectionPoolMetricsAutoConfiguration,\
7777
org.springframework.boot.actuate.autoconfigure.metrics.redis.LettuceMetricsAutoConfiguration,\
78-
org.springframework.boot.actuate.autoconfigure.metrics.startup.StartupTimeMetricsAutoConfiguration,\
78+
org.springframework.boot.actuate.autoconfigure.metrics.startup.StartupTimeMetricsListenerAutoConfiguration,\
7979
org.springframework.boot.actuate.autoconfigure.metrics.task.TaskExecutorMetricsAutoConfiguration,\
8080
org.springframework.boot.actuate.autoconfigure.metrics.web.client.HttpClientMetricsAutoConfiguration,\
8181
org.springframework.boot.actuate.autoconfigure.metrics.web.jetty.JettyMetricsAutoConfiguration,\
Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
import org.springframework.boot.SpringApplication;
2727
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
28-
import org.springframework.boot.actuate.metrics.startup.StartupTimeMetrics;
28+
import org.springframework.boot.actuate.metrics.startup.StartupTimeMetricsListener;
2929
import org.springframework.boot.autoconfigure.AutoConfigurations;
3030
import org.springframework.boot.context.event.ApplicationReadyEvent;
3131
import org.springframework.boot.context.event.ApplicationStartedEvent;
@@ -35,20 +35,20 @@
3535
import static org.mockito.Mockito.mock;
3636

3737
/**
38-
* Tests for {@link StartupTimeMetricsAutoConfiguration}.
38+
* Tests for {@link StartupTimeMetricsListenerAutoConfiguration}.
3939
*
4040
* @author Chris Bono
4141
* @author Stephane Nicoll
4242
*/
43-
class StartupTimeMetricsAutoConfigurationTests {
43+
class StartupTimeMetricsListenerAutoConfigurationTests {
4444

4545
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().with(MetricsRun.simple())
46-
.withConfiguration(AutoConfigurations.of(StartupTimeMetricsAutoConfiguration.class));
46+
.withConfiguration(AutoConfigurations.of(StartupTimeMetricsListenerAutoConfiguration.class));
4747

4848
@Test
4949
void startupTimeMetricsAreRecorded() {
5050
this.contextRunner.run((context) -> {
51-
assertThat(context).hasSingleBean(StartupTimeMetrics.class);
51+
assertThat(context).hasSingleBean(StartupTimeMetricsListener.class);
5252
SimpleMeterRegistry registry = context.getBean(SimpleMeterRegistry.class);
5353
context.publishEvent(new ApplicationStartedEvent(new SpringApplication(), null,
5454
context.getSourceApplicationContext(), Duration.ofMillis(1500)));
@@ -80,8 +80,9 @@ void startupTimeMetricsCanBeDisabled() {
8080
@Test
8181
void customStartupTimeMetricsAreRespected() {
8282
this.contextRunner
83-
.withBean("customStartupTimeMetrics", StartupTimeMetrics.class, () -> mock(StartupTimeMetrics.class))
84-
.run((context) -> assertThat(context).hasSingleBean(StartupTimeMetrics.class)
83+
.withBean("customStartupTimeMetrics", StartupTimeMetricsListener.class,
84+
() -> mock(StartupTimeMetricsListener.class))
85+
.run((context) -> assertThat(context).hasSingleBean(StartupTimeMetricsListener.class)
8586
.hasBean("customStartupTimeMetrics"));
8687
}
8788

Lines changed: 28 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@
3636
* {@link ApplicationReadyEvent}.
3737
*
3838
* @author Chris Bono
39+
* @author Phillip Webb
3940
* @since 2.6.0
4041
*/
41-
public class StartupTimeMetrics implements SmartApplicationListener {
42+
public class StartupTimeMetricsListener implements SmartApplicationListener {
4243

4344
/**
4445
* The default name to use for the application started time metric.
@@ -52,38 +53,37 @@ public class StartupTimeMetrics implements SmartApplicationListener {
5253

5354
private final MeterRegistry meterRegistry;
5455

55-
private final String applicationStartedTimeMetricName;
56+
private final String startedTimeMetricName;
5657

57-
private final String applicationReadyTimeMetricName;
58+
private final String readyTimeMetricName;
5859

59-
private final Iterable<Tag> tags;
60+
private final Tags tags;
6061

6162
/**
6263
* Create a new instance using default metric names.
6364
* @param meterRegistry the registry to use
6465
* @see #APPLICATION_STARTED_TIME_METRIC_NAME
6566
* @see #APPLICATION_READY_TIME_METRIC_NAME
6667
*/
67-
public StartupTimeMetrics(MeterRegistry meterRegistry) {
68-
this(meterRegistry, Collections.emptyList(), APPLICATION_STARTED_TIME_METRIC_NAME,
69-
APPLICATION_READY_TIME_METRIC_NAME);
68+
public StartupTimeMetricsListener(MeterRegistry meterRegistry) {
69+
this(meterRegistry, APPLICATION_STARTED_TIME_METRIC_NAME, APPLICATION_READY_TIME_METRIC_NAME,
70+
Collections.emptyList());
7071
}
7172

7273
/**
7374
* Create a new instance using the specified options.
7475
* @param meterRegistry the registry to use
76+
* @param startedTimeMetricName the name to use for the application started time
77+
* metric
78+
* @param readyTimeMetricName the name to use for the application ready time metric
7579
* @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
8080
*/
81-
public StartupTimeMetrics(MeterRegistry meterRegistry, Iterable<Tag> tags, String applicationStartedTimeMetricName,
82-
String applicationReadyTimeMetricName) {
81+
public StartupTimeMetricsListener(MeterRegistry meterRegistry, String startedTimeMetricName,
82+
String readyTimeMetricName, Iterable<Tag> tags) {
8383
this.meterRegistry = meterRegistry;
84-
this.tags = (tags != null) ? tags : Collections.emptyList();
85-
this.applicationStartedTimeMetricName = applicationStartedTimeMetricName;
86-
this.applicationReadyTimeMetricName = applicationReadyTimeMetricName;
84+
this.startedTimeMetricName = startedTimeMetricName;
85+
this.readyTimeMetricName = readyTimeMetricName;
86+
this.tags = Tags.of(tags);
8787
}
8888

8989
@Override
@@ -103,33 +103,27 @@ public void onApplicationEvent(ApplicationEvent event) {
103103
}
104104

105105
private void onApplicationStarted(ApplicationStartedEvent event) {
106-
if (event.getStartedTime() == null) {
107-
return;
108-
}
109-
registerGauge(this.applicationStartedTimeMetricName, "Time taken (ms) to start the application",
110-
event.getStartedTime(), createTagsFrom(event.getSpringApplication()));
106+
registerGauge(this.startedTimeMetricName, "Time taken (ms) to start the application", event.getTimeTaken(),
107+
event.getSpringApplication());
111108
}
112109

113110
private void onApplicationReady(ApplicationReadyEvent event) {
114-
if (event.getReadyTime() == null) {
115-
return;
116-
}
117-
registerGauge(this.applicationReadyTimeMetricName,
118-
"Time taken (ms) for the application to be ready to service requests", event.getReadyTime(),
119-
createTagsFrom(event.getSpringApplication()));
111+
registerGauge(this.readyTimeMetricName, "Time taken (ms) for the application to be ready to service requests",
112+
event.getTimeTaken(), event.getSpringApplication());
120113
}
121114

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)
124-
.register(this.meterRegistry);
115+
private void registerGauge(String name, String description, Duration timeTaken,
116+
SpringApplication springApplication) {
117+
if (timeTaken != null) {
118+
Iterable<Tag> tags = createTagsFrom(springApplication);
119+
TimeGauge.builder(name, timeTaken::toMillis, TimeUnit.MILLISECONDS).tags(tags).description(description)
120+
.register(this.meterRegistry);
121+
}
125122
}
126123

127124
private Iterable<Tag> createTagsFrom(SpringApplication springApplication) {
128125
Class<?> mainClass = springApplication.getMainApplicationClass();
129-
if (mainClass == null) {
130-
return this.tags;
131-
}
132-
return Tags.concat(this.tags, "main-application-class", mainClass.getName());
126+
return (mainClass != null) ? this.tags.and("main-application-class", mainClass.getName()) : this.tags;
133127
}
134128

135129
}
Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,44 +35,44 @@
3535
import static org.mockito.Mockito.mock;
3636

3737
/**
38-
* Tests for {@link StartupTimeMetrics}.
38+
* Tests for {@link StartupTimeMetricsListener}.
3939
*
4040
* @author Chris Bono
4141
*/
42-
class StartupTimeMetricsTests {
42+
class StartupTimeMetricsListenerTests {
4343

4444
private MeterRegistry registry;
4545

46-
private StartupTimeMetrics metrics;
46+
private StartupTimeMetricsListener listener;
4747

4848
@BeforeEach
4949
void setup() {
5050
this.registry = new SimpleMeterRegistry();
51-
this.metrics = new StartupTimeMetrics(this.registry);
51+
this.listener = new StartupTimeMetricsListener(this.registry);
5252
}
5353

5454
@Test
5555
void metricsRecordedWithoutCustomTags() {
56-
this.metrics.onApplicationEvent(applicationStartedEvent(2000L));
57-
this.metrics.onApplicationEvent(applicationReadyEvent(2200L));
56+
this.listener.onApplicationEvent(applicationStartedEvent(2000L));
57+
this.listener.onApplicationEvent(applicationReadyEvent(2200L));
5858
assertMetricExistsWithValue("application.started.time", 2000L);
5959
assertMetricExistsWithValue("application.ready.time", 2200L);
6060
}
6161

6262
@Test
6363
void metricsRecordedWithCustomTagsAndMetricNames() {
6464
Tags tags = Tags.of("foo", "bar");
65-
this.metrics = new StartupTimeMetrics(this.registry, tags, "m1", "m2");
66-
this.metrics.onApplicationEvent(applicationStartedEvent(1000L));
67-
this.metrics.onApplicationEvent(applicationReadyEvent(1050L));
65+
this.listener = new StartupTimeMetricsListener(this.registry, "m1", "m2", tags);
66+
this.listener.onApplicationEvent(applicationStartedEvent(1000L));
67+
this.listener.onApplicationEvent(applicationReadyEvent(1050L));
6868
assertMetricExistsWithCustomTagsAndValue("m1", tags, 1000L);
6969
assertMetricExistsWithCustomTagsAndValue("m2", tags, 1050L);
7070
}
7171

7272
@Test
7373
void metricRecordedWithoutMainAppClassTag() {
7474
SpringApplication application = mock(SpringApplication.class);
75-
this.metrics.onApplicationEvent(new ApplicationStartedEvent(application, null, null, Duration.ofSeconds(2)));
75+
this.listener.onApplicationEvent(new ApplicationStartedEvent(application, null, null, Duration.ofSeconds(2)));
7676
TimeGauge applicationStartedGague = this.registry.find("application.started.time").timeGauge();
7777
assertThat(applicationStartedGague).isNotNull();
7878
assertThat(applicationStartedGague.getId().getTags()).isEmpty();
@@ -82,17 +82,17 @@ void metricRecordedWithoutMainAppClassTag() {
8282
void metricRecordedWithoutMainAppClassTagAndAdditionalTags() {
8383
SpringApplication application = mock(SpringApplication.class);
8484
Tags tags = Tags.of("foo", "bar");
85-
this.metrics = new StartupTimeMetrics(this.registry, tags, "started", "ready");
86-
this.metrics.onApplicationEvent(new ApplicationReadyEvent(application, null, null, Duration.ofSeconds(2)));
85+
this.listener = new StartupTimeMetricsListener(this.registry, "started", "ready", tags);
86+
this.listener.onApplicationEvent(new ApplicationReadyEvent(application, null, null, Duration.ofSeconds(2)));
8787
TimeGauge applicationReadyGague = this.registry.find("ready").timeGauge();
8888
assertThat(applicationReadyGague).isNotNull();
8989
assertThat(applicationReadyGague.getId().getTags()).containsExactlyElementsOf(tags);
9090
}
9191

9292
@Test
9393
void metricsNotRecordedWhenStartupTimeNotAvailable() {
94-
this.metrics.onApplicationEvent(applicationStartedEvent(null));
95-
this.metrics.onApplicationEvent(applicationReadyEvent(null));
94+
this.listener.onApplicationEvent(applicationStartedEvent(null));
95+
this.listener.onApplicationEvent(applicationReadyEvent(null));
9696
assertThat(this.registry.find("application.started.time").timeGauge()).isNull();
9797
assertThat(this.registry.find("application.ready.time").timeGauge()).isNull();
9898
}

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplication.java

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@
7878
import org.springframework.util.CollectionUtils;
7979
import org.springframework.util.ObjectUtils;
8080
import org.springframework.util.ReflectionUtils;
81-
import org.springframework.util.StopWatch;
8281
import org.springframework.util.StringUtils;
8382

8483
/**
@@ -286,8 +285,7 @@ private Class<?> deduceMainApplicationClass() {
286285
* @return a running {@link ApplicationContext}
287286
*/
288287
public ConfigurableApplicationContext run(String... args) {
289-
StopWatch stopWatch = new StopWatch();
290-
stopWatch.start();
288+
long startTime = System.nanoTime();
291289
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
292290
ConfigurableApplicationContext context = null;
293291
configureHeadlessProperty();
@@ -303,23 +301,20 @@ public ConfigurableApplicationContext run(String... args) {
303301
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
304302
refreshContext(context);
305303
afterRefresh(context, applicationArguments);
306-
stopWatch.stop();
307-
Duration startedTime = Duration.ofMillis(stopWatch.getTotalTimeMillis());
308-
stopWatch.start();
304+
Duration timeTakeToStartup = Duration.ofNanos(System.nanoTime() - startTime);
309305
if (this.logStartupInfo) {
310-
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), startedTime);
306+
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakeToStartup);
311307
}
312-
listeners.started(context, startedTime);
308+
listeners.started(context, timeTakeToStartup);
313309
callRunners(context, applicationArguments);
314310
}
315311
catch (Throwable ex) {
316312
handleRunFailure(context, ex, listeners);
317313
throw new IllegalStateException(ex);
318314
}
319-
320315
try {
321-
stopWatch.stop();
322-
listeners.running(context, Duration.ofMillis(stopWatch.getTotalTimeMillis()));
316+
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
317+
listeners.ready(context, timeTakenToReady);
323318
}
324319
catch (Throwable ex) {
325320
handleRunFailure(context, ex, null);

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationRunListener.java

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -77,53 +77,50 @@ default void contextLoaded(ConfigurableApplicationContext context) {
7777
* {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
7878
* ApplicationRunners} have not been called.
7979
* @param context the application context.
80-
* @since 2.0.0
81-
* @deprecated since 2.6.0 for removal in 2.8.0 in favour of
82-
* {@link #started(ConfigurableApplicationContext, Duration)}
80+
* @param timeTaken the time taken to start the application or {@code null} if unknown
81+
* @since 2.6.0
8382
*/
84-
@Deprecated
85-
default void started(ConfigurableApplicationContext context) {
86-
started(context, null);
83+
default void started(ConfigurableApplicationContext context, Duration timeTaken) {
84+
started(context);
8785
}
8886

8987
/**
9088
* The context has been refreshed and the application has started but
9189
* {@link CommandLineRunner CommandLineRunners} and {@link ApplicationRunner
9290
* ApplicationRunners} have not been called.
9391
* @param context the application context.
94-
* @param startedTime the time taken to start the application or {@code null} if
95-
* unknown
96-
* @since 2.6.0
92+
* @since 2.0.0
93+
* @deprecated since 2.6.0 for removal in 2.8.0 in favor of
94+
* {@link #started(ConfigurableApplicationContext, Duration)}
9795
*/
98-
default void started(ConfigurableApplicationContext context, Duration startedTime) {
99-
started(context);
96+
@Deprecated
97+
default void started(ConfigurableApplicationContext context) {
10098
}
10199

102100
/**
103101
* Called immediately before the run method finishes, when the application context has
104102
* been refreshed and all {@link CommandLineRunner CommandLineRunners} and
105103
* {@link ApplicationRunner ApplicationRunners} have been called.
106104
* @param context the application context.
107-
* @deprecated since 2.6.0 for removal in 2.8.0 in favour of
108-
* {@link #running(ConfigurableApplicationContext, Duration)}
109-
* @since 2.0.0
105+
* @param timeTaken the time taken for the application to be ready or {@code null} if
106+
* unknown
107+
* @since 2.6.0
110108
*/
111-
@Deprecated
112-
default void running(ConfigurableApplicationContext context) {
113-
running(context, null);
109+
default void ready(ConfigurableApplicationContext context, Duration timeTaken) {
110+
running(context);
114111
}
115112

116113
/**
117114
* Called immediately before the run method finishes, when the application context has
118115
* been refreshed and all {@link CommandLineRunner CommandLineRunners} and
119116
* {@link ApplicationRunner ApplicationRunners} have been called.
120117
* @param context the application context.
121-
* @param readyTime the time taken for the application to be ready to service requests
122-
* or {@code null} if unknown
123-
* @since 2.6.0
118+
* @deprecated since 2.6.0 for removal in 2.8.0 in favor of
119+
* {@link #ready(ConfigurableApplicationContext, Duration)}
120+
* @since 2.0.0
124121
*/
125-
default void running(ConfigurableApplicationContext context, Duration readyTime) {
126-
running(context);
122+
@Deprecated
123+
default void running(ConfigurableApplicationContext context) {
127124
}
128125

129126
/**

0 commit comments

Comments
 (0)