Polish "Add startup time metrics"

See gh-27878
This commit is contained in:
Stephane Nicoll
2021-09-16 10:00:39 +02:00
parent 2e67963bfe
commit c62a6819fe
19 changed files with 201 additions and 185 deletions

View File

@@ -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;

View File

@@ -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 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 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 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 {