diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java index 910e3d67b3b..a011ddf7b96 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java @@ -16,25 +16,21 @@ package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus; -import java.net.UnknownHostException; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import javax.annotation.PreDestroy; +import java.time.Duration; +import java.util.Map; import io.micrometer.core.instrument.Clock; import io.micrometer.prometheus.PrometheusConfig; import io.micrometer.prometheus.PrometheusMeterRegistry; import io.prometheus.client.CollectorRegistry; import io.prometheus.client.exporter.PushGateway; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.ShutdownOperation; import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureBefore; @@ -53,6 +49,7 @@ import org.springframework.core.env.Environment; * * @since 2.0.0 * @author Jon Schneider + * @author David J. M. Karlsen */ @Configuration @AutoConfigureBefore({ CompositeMeterRegistryAutoConfiguration.class, @@ -100,108 +97,42 @@ public class PrometheusMetricsExportAutoConfiguration { /** * Configuration for Prometheus * Pushgateway. - * - * @author David J. M. Karlsen */ @Configuration @ConditionalOnClass(PushGateway.class) @ConditionalOnProperty(prefix = "management.metrics.export.prometheus.pushgateway", name = "enabled") public static class PrometheusPushGatewayConfiguration { + /** + * The fallback job name. We use 'spring' since there's a history of Prometheus + * spring integration defaulting to that name from when Prometheus integration + * didn't exist in Spring itself. + */ + private static final String FALLBACK_JOB = "spring"; + @Bean - public PushGatewayHandler pushGatewayHandler(CollectorRegistry collectorRegistry, + @ConditionalOnMissingBean + public PrometheusPushGatewayManager prometheusPushGatewayManager( + CollectorRegistry collectorRegistry, PrometheusProperties prometheusProperties, Environment environment) { - return new PushGatewayHandler(collectorRegistry, prometheusProperties, - environment); + PrometheusProperties.Pushgateway properties = prometheusProperties + .getPushgateway(); + PushGateway pushGateway = new PushGateway(properties.getBaseUrl()); + Duration pushRate = properties.getPushRate(); + String job = getJob(properties, environment); + Map groupingKey = properties.getGroupingKey(); + ShutdownOperation shutdownOperation = properties.getShutdownOperation(); + return new PrometheusPushGatewayManager(pushGateway, collectorRegistry, + pushRate, job, groupingKey, shutdownOperation); } - static class PushGatewayHandler { - - private final Logger logger = LoggerFactory - .getLogger(PrometheusPushGatewayConfiguration.class); - - private final CollectorRegistry collectorRegistry; - - private final PrometheusProperties.PushgatewayProperties pushgatewayProperties; - - private final PushGateway pushGateway; - - private final Environment environment; - - private final ScheduledExecutorService executorService; - - PushGatewayHandler(CollectorRegistry collectorRegistry, - PrometheusProperties prometheusProperties, Environment environment) { - this.collectorRegistry = collectorRegistry; - this.pushgatewayProperties = prometheusProperties.getPushgateway(); - this.pushGateway = new PushGateway( - this.pushgatewayProperties.getBaseUrl()); - this.environment = environment; - this.executorService = Executors.newSingleThreadScheduledExecutor((r) -> { - Thread thread = new Thread(r); - thread.setDaemon(true); - thread.setName("micrometer-pushgateway"); - return thread; - }); - this.executorService.scheduleAtFixedRate(this::push, 0, - this.pushgatewayProperties.getPushRate().toMillis(), - TimeUnit.MILLISECONDS); - } - - void push() { - try { - this.pushGateway.pushAdd(this.collectorRegistry, getJobName(), - this.pushgatewayProperties.getGroupingKeys()); - } - catch (UnknownHostException ex) { - this.logger.error("Unable to locate host '" - + this.pushgatewayProperties.getBaseUrl() - + "'. No longer attempting metrics publication to this host"); - this.executorService.shutdown(); - } - catch (Throwable throwable) { - this.logger.error("Unable to push metrics to Prometheus Pushgateway", - throwable); - } - } - - @PreDestroy - void shutdown() { - this.executorService.shutdown(); - if (this.pushgatewayProperties.isPushOnShutdown()) { - push(); - } - if (this.pushgatewayProperties.isDeleteOnShutdown()) { - delete(); - } - } - - private void delete() { - try { - this.pushGateway.delete(getJobName(), - this.pushgatewayProperties.getGroupingKeys()); - } - catch (Throwable throwable) { - this.logger.error( - "Unable to delete metrics from Prometheus Pushgateway", - throwable); - } - } - - private String getJobName() { - String job = this.pushgatewayProperties.getJob(); - if (job == null) { - job = this.environment.getProperty("spring.application.name"); - } - if (job == null) { - // There's a history of Prometheus spring integration defaulting the - // getJobName name to "spring" from when - // Prometheus integration didn't exist in Spring itself. - job = "spring"; - } - return job; - } - + private String getJob(PrometheusProperties.Pushgateway properties, + Environment environment) { + String job = properties.getJob(); + job = (job != null) ? job + : environment.getProperty("spring.application.name"); + job = (job != null) ? job : FALLBACK_JOB; + return job; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java index a1650e1937d..0fc7a9ccb1a 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java @@ -20,6 +20,7 @@ import java.time.Duration; import java.util.HashMap; import java.util.Map; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.ShutdownOperation; import org.springframework.boot.context.properties.ConfigurationProperties; /** @@ -42,7 +43,7 @@ public class PrometheusProperties { * Configuration options for using Prometheus Pushgateway, allowing metrics to be * pushed when they cannot be scraped. */ - private PushgatewayProperties pushgateway = new PushgatewayProperties(); + private Pushgateway pushgateway = new Pushgateway(); /** * Step size (i.e. reporting frequency) to use. @@ -65,18 +66,18 @@ public class PrometheusProperties { this.step = step; } - public PushgatewayProperties getPushgateway() { + public Pushgateway getPushgateway() { return this.pushgateway; } - public void setPushgateway(PushgatewayProperties pushgateway) { + public void setPushgateway(Pushgateway pushgateway) { this.pushgateway = pushgateway; } /** * Configuration options for push-based interaction with Prometheus. */ - public static class PushgatewayProperties { + public static class Pushgateway { /** * Enable publishing via a Prometheus Pushgateway. @@ -84,34 +85,29 @@ public class PrometheusProperties { private Boolean enabled = false; /** - * Required host:port or ip:port of the Pushgateway. + * Base URL for the Pushgateway. */ private String baseUrl = "localhost:9091"; /** - * Required identifier for this application instance. - */ - private String job; - - /** - * Frequency with which to push metrics to Pushgateway. + * Frequency with which to push metrics. */ private Duration pushRate = Duration.ofMinutes(1); /** - * Push metrics right before shut-down. Mostly useful for batch jobs. + * Job identifier for this application instance. */ - private boolean pushOnShutdown = true; + private String job; /** - * Delete metrics from Pushgateway when application is shut-down. + * Grouping key for the pushed metrics. */ - private boolean deleteOnShutdown = true; + private Map groupingKey = new HashMap<>(); /** - * Used to group metrics in pushgateway. A common example is setting + * Operation that should be performed on shutdown. */ - private Map groupingKeys = new HashMap<>(); + private ShutdownOperation shutdownOperation = ShutdownOperation.NONE; public Boolean getEnabled() { return this.enabled; @@ -129,14 +125,6 @@ public class PrometheusProperties { this.baseUrl = baseUrl; } - public String getJob() { - return this.job; - } - - public void setJob(String job) { - this.job = job; - } - public Duration getPushRate() { return this.pushRate; } @@ -145,28 +133,28 @@ public class PrometheusProperties { this.pushRate = pushRate; } - public boolean isPushOnShutdown() { - return this.pushOnShutdown; + public String getJob() { + return this.job; } - public void setPushOnShutdown(boolean pushOnShutdown) { - this.pushOnShutdown = pushOnShutdown; + public void setJob(String job) { + this.job = job; } - public boolean isDeleteOnShutdown() { - return this.deleteOnShutdown; + public Map getGroupingKey() { + return this.groupingKey; } - public void setDeleteOnShutdown(boolean deleteOnShutdown) { - this.deleteOnShutdown = deleteOnShutdown; + public void setGroupingKey(Map groupingKey) { + this.groupingKey = groupingKey; } - public Map getGroupingKeys() { - return this.groupingKeys; + public ShutdownOperation getShutdownOperation() { + return this.shutdownOperation; } - public void setGroupingKeys(Map groupingKeys) { - this.groupingKeys = groupingKeys; + public void setShutdownOperation(ShutdownOperation shutdownOperation) { + this.shutdownOperation = shutdownOperation; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java index 56155d6739b..52f80208d80 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java @@ -22,8 +22,8 @@ import io.micrometer.prometheus.PrometheusMeterRegistry; import io.prometheus.client.CollectorRegistry; import org.junit.Test; -import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration.PrometheusPushGatewayConfiguration; import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager; import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -137,8 +137,8 @@ public class PrometheusMetricsExportAutoConfigurationTests { .withPropertyValues( "management.metrics.export.prometheus.pushgateway.enabled=true") .withUserConfiguration(BaseConfiguration.class) - .run((context) -> assertThat(context).hasSingleBean( - PrometheusPushGatewayConfiguration.PushGatewayHandler.class)); + .run((context) -> assertThat(context) + .hasSingleBean(PrometheusPushGatewayManager.class)); } @Configuration diff --git a/spring-boot-project/spring-boot-actuator/pom.xml b/spring-boot-project/spring-boot-actuator/pom.xml index ba0b2dd3419..a02ba2d1e77 100644 --- a/spring-boot-project/spring-boot-actuator/pom.xml +++ b/spring-boot-project/spring-boot-actuator/pom.xml @@ -1,5 +1,6 @@ - 4.0.0 @@ -61,6 +62,11 @@ micrometer-registry-prometheus true + + io.prometheus + simpleclient_pushgateway + true + io.reactivex rxjava-reactive-streams diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java new file mode 100644 index 00000000000..9e9e50b3024 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManager.java @@ -0,0 +1,201 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.export.prometheus; + +import java.net.UnknownHostException; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; + +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exporter.PushGateway; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Class that can be used to managed the pushing of metrics to a {@link PushGateway + * Prometheus PushGateway}. Handles the scheduling of push operations, error handling and + * shutdown operations. + * + * @author David J. M. Karlsen + * @author Phillip Webb + * @since 2.1.0 + */ +public class PrometheusPushGatewayManager { + + private static final Logger logger = LoggerFactory + .getLogger(PrometheusPushGatewayManager.class); + + private final PushGateway pushGateway; + + private final CollectorRegistry registry; + + private final String job; + + private final Map groupingKey; + + private final ShutdownOperation shutdownOperation; + + private final TaskScheduler scheduler; + + private ScheduledFuture scheduled; + + /** + * Create a new {@link PrometheusPushGatewayManager} instance using a single threaded + * {@link TaskScheduler}. + * @param pushGateway the source push gateway + * @param registry the collector registry to push + * @param pushRate the rate at which push operations occur + * @param job the job ID for the operation + * @param groupingKeys an optional set of grouping keys for the operation + * @param shutdownOperation the shutdown operation that should be performed when + * context is closed. + */ + public PrometheusPushGatewayManager(PushGateway pushGateway, + CollectorRegistry registry, Duration pushRate, String job, + Map groupingKeys, ShutdownOperation shutdownOperation) { + this(pushGateway, registry, new PushGatewayTaskScheduler(), pushRate, job, + groupingKeys, shutdownOperation); + } + + /** + * Create a new {@link PrometheusPushGatewayManager} instance. + * @param pushGateway the source push gateway + * @param registry the collector registry to push + * @param scheduler the scheduler used for operations + * @param pushRate the rate at which push operations occur + * @param job the job ID for the operation + * @param groupingKey an optional set of grouping keys for the operation + * @param shutdownOperation the shutdown operation that should be performed when + * context is closed. + */ + public PrometheusPushGatewayManager(PushGateway pushGateway, + CollectorRegistry registry, TaskScheduler scheduler, Duration pushRate, + String job, Map groupingKey, + ShutdownOperation shutdownOperation) { + Assert.notNull(pushGateway, "PushGateway must not be null"); + Assert.notNull(registry, "Registry must not be null"); + Assert.notNull(scheduler, "Scheduler must not be null"); + Assert.notNull(pushRate, "PushRate must not be null"); + Assert.hasLength(job, "Job must not be empty"); + this.pushGateway = pushGateway; + this.registry = registry; + this.job = job; + this.groupingKey = groupingKey; + this.shutdownOperation = (shutdownOperation != null) ? shutdownOperation + : ShutdownOperation.NONE; + this.scheduler = scheduler; + this.scheduled = this.scheduler.scheduleAtFixedRate(this::push, pushRate); + } + + private void push() { + try { + this.pushGateway.pushAdd(this.registry, this.job, this.groupingKey); + } + catch (UnknownHostException ex) { + String host = ex.getMessage(); + String message = "Unable to locate prometheus push gateway host"; + message += StringUtils.hasLength(host) ? " '" + host + "'" : ""; + message += ". No longer attempting metrics publication to this host"; + logger.error(message, ex); + shutdown(ShutdownOperation.NONE); + } + catch (Throwable ex) { + logger.error("Unable to push metrics to Prometheus Pushgateway", ex); + } + } + + private void delete() { + try { + this.pushGateway.delete(this.job, this.groupingKey); + } + catch (Throwable ex) { + logger.error("Unable to delete metrics from Prometheus Pushgateway", ex); + } + } + + /** + * Shutdown the manager, running any {@link ShutdownOperation}. + */ + public void shutdown() { + shutdown(this.shutdownOperation); + } + + private void shutdown(ShutdownOperation shutdownOperation) { + if (this.scheduler instanceof PushGatewayTaskScheduler) { + ((PushGatewayTaskScheduler) this.scheduler).shutdown(); + } + this.scheduled.cancel(false); + switch (shutdownOperation) { + case PUSH: + push(); + break; + case DELETE: + delete(); + break; + } + } + + /** + * The operation that should be performed on shutdown. + */ + public enum ShutdownOperation { + + /** + * Don't perform any shutdown operation. + */ + NONE, + + /** + * Perform a 'push' before shutdown. + */ + PUSH, + + /** + * Perform a 'delete' before shutdown. + */ + DELETE + + } + + /** + * {@link TaskScheduler} used when the user doesn't specify one. + */ + static class PushGatewayTaskScheduler extends ThreadPoolTaskScheduler { + + PushGatewayTaskScheduler() { + setPoolSize(1); + setDaemon(true); + setThreadGroupName("prometheus-push-gateway"); + } + + @Override + public ScheduledExecutorService getScheduledExecutor() + throws IllegalStateException { + return Executors.newSingleThreadScheduledExecutor(this::newThread); + } + + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java new file mode 100644 index 00000000000..acb382213a0 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/export/prometheus/PrometheusPushGatewayManagerTests.java @@ -0,0 +1,215 @@ +/* + * Copyright 2012-2018 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 + * + * http://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.export.prometheus; + +import java.net.UnknownHostException; +import java.time.Duration; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; + +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exporter.PushGateway; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.PushGatewayTaskScheduler; +import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusPushGatewayManager.ShutdownOperation; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; + +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Tests for {@link PrometheusPushGatewayManager}. + * + * @author Phillip Webb + */ +public class PrometheusPushGatewayManagerTests { + + @Mock + private PushGateway pushGateway; + + @Mock + private CollectorRegistry registry; + + private TaskScheduler scheduler; + + private Duration pushRate = Duration.ofSeconds(1); + + private Map groupingKey = Collections.singletonMap("foo", "bar"); + + @Captor + private ArgumentCaptor task; + + @Mock + private ScheduledFuture future; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + this.scheduler = mockScheduler(TaskScheduler.class); + } + + @Test + public void createWhenPushGatewayIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new PrometheusPushGatewayManager(null, this.registry, + this.scheduler, this.pushRate, "job", this.groupingKey, null)) + .withMessage("PushGateway must not be null"); + } + + @Test + public void createWhenCollectorRegistryIsNullThrowsException() { + assertThatIllegalArgumentException() + .isThrownBy(() -> new PrometheusPushGatewayManager(this.pushGateway, null, + this.scheduler, this.pushRate, "job", this.groupingKey, null)) + .withMessage("Registry must not be null"); + } + + @Test + public void createWhenSchedulerIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy( + () -> new PrometheusPushGatewayManager(this.pushGateway, this.registry, + null, this.pushRate, "job", this.groupingKey, null)) + .withMessage("Scheduler must not be null"); + } + + @Test + public void createWhenPushRateIsNullThrowsException() { + assertThatIllegalArgumentException().isThrownBy( + () -> new PrometheusPushGatewayManager(this.pushGateway, this.registry, + this.scheduler, null, "job", this.groupingKey, null)) + .withMessage("PushRate must not be null"); + } + + @Test + public void createWhenJobIsEmptyThrowsException() { + assertThatIllegalArgumentException().isThrownBy( + () -> new PrometheusPushGatewayManager(this.pushGateway, this.registry, + this.scheduler, this.pushRate, "", this.groupingKey, null)) + .withMessage("Job must not be empty"); + } + + @Test + public void createShouldSchedulePushAsFixedRate() throws Exception { + new PrometheusPushGatewayManager(this.pushGateway, this.registry, this.scheduler, + this.pushRate, "job", this.groupingKey, null); + verify(this.scheduler).scheduleAtFixedRate(this.task.capture(), + eq(this.pushRate)); + this.task.getValue().run(); + verify(this.pushGateway).pushAdd(this.registry, "job", this.groupingKey); + } + + @Test + public void shutdownWhenOwnsSchedulerDoesShutdownScheduler() { + PushGatewayTaskScheduler ownedScheduler = mockScheduler( + PushGatewayTaskScheduler.class); + PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager( + this.pushGateway, this.registry, ownedScheduler, this.pushRate, "job", + this.groupingKey, null); + manager.shutdown(); + verify(ownedScheduler).shutdown(); + } + + @Test + public void shutdownWhenDoesNotOwnSchedulerDoesNotShutdownScheduler() { + ThreadPoolTaskScheduler otherScheduler = mockScheduler( + ThreadPoolTaskScheduler.class); + PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager( + this.pushGateway, this.registry, otherScheduler, this.pushRate, "job", + this.groupingKey, null); + manager.shutdown(); + verify(otherScheduler, never()).shutdown(); + } + + @Test + public void shutdownWhenShutdownOperationIsPushPerformsPushOnShutdown() + throws Exception { + PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager( + this.pushGateway, this.registry, this.scheduler, this.pushRate, "job", + this.groupingKey, ShutdownOperation.PUSH); + manager.shutdown(); + verify(this.future).cancel(false); + verify(this.pushGateway).pushAdd(this.registry, "job", this.groupingKey); + } + + @Test + public void shutdownWhenShutdownOperationIsDeletePerformsDeleteOnShutdown() + throws Exception { + PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager( + this.pushGateway, this.registry, this.scheduler, this.pushRate, "job", + this.groupingKey, ShutdownOperation.DELETE); + manager.shutdown(); + verify(this.future).cancel(false); + verify(this.pushGateway).delete("job", this.groupingKey); + } + + @Test + public void shutdownWhenShutdownOperationIsNoneDoesNothing() { + PrometheusPushGatewayManager manager = new PrometheusPushGatewayManager( + this.pushGateway, this.registry, this.scheduler, this.pushRate, "job", + this.groupingKey, ShutdownOperation.NONE); + manager.shutdown(); + verify(this.future).cancel(false); + verifyZeroInteractions(this.pushGateway); + } + + @Test + public void pushWhenUnknownHostExceptionIsThrownDoesShutdown() throws Exception { + new PrometheusPushGatewayManager(this.pushGateway, this.registry, this.scheduler, + this.pushRate, "job", this.groupingKey, null); + verify(this.scheduler).scheduleAtFixedRate(this.task.capture(), + eq(this.pushRate)); + willThrow(new UnknownHostException("foo")).given(this.pushGateway) + .pushAdd(this.registry, "job", this.groupingKey); + this.task.getValue().run(); + verify(this.future).cancel(false); + } + + @Test + public void pushDoesNotThrowException() throws Exception { + new PrometheusPushGatewayManager(this.pushGateway, this.registry, this.scheduler, + this.pushRate, "job", this.groupingKey, null); + verify(this.scheduler).scheduleAtFixedRate(this.task.capture(), + eq(this.pushRate)); + willThrow(RuntimeException.class).given(this.pushGateway).pushAdd(this.registry, + "job", this.groupingKey); + this.task.getValue().run(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private T mockScheduler(Class type) { + T scheduler = mock(type); + given(scheduler.scheduleAtFixedRate(isA(Runnable.class), isA(Duration.class))) + .willReturn((ScheduledFuture) this.future); + return scheduler; + } + +} diff --git a/spring-boot-project/spring-boot-dependencies/pom.xml b/spring-boot-project/spring-boot-dependencies/pom.xml index e85932c32d3..c3f89051a79 100644 --- a/spring-boot-project/spring-boot-dependencies/pom.xml +++ b/spring-boot-project/spring-boot-dependencies/pom.xml @@ -143,7 +143,6 @@ 1.1.0 1.0.3 42.2.5 - 0.5.0 2.3.0 4.2.1 diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc index e638bc3967f..ea5b8de2daf 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/appendix-application-properties.adoc @@ -1480,6 +1480,12 @@ content into your application. Rather, pick only the properties that you need. management.metrics.export.prometheus.descriptions=true # Whether to enable publishing descriptions as part of the scrape payload to Prometheus. Turn this off to minimize the amount of data sent on each scrape. management.metrics.export.prometheus.enabled=true # Whether exporting of metrics to Prometheus is enabled. management.metrics.export.prometheus.step=1m # Step size (i.e. reporting frequency) to use. + management.metrics.export.prometheus.pushgateway.base-url=localhost:9091 # Base URL for the Pushgateway. + management.metrics.export.prometheus.pushgateway.enabled=false # Enable publishing via a Prometheus Pushgateway. + management.metrics.export.prometheus.pushgateway.grouping-key= # Grouping key for the pushed metrics. + management.metrics.export.prometheus.pushgateway.job= # Job identifier for this application instance. + management.metrics.export.prometheus.pushgateway.push-rate=1m # Frequency with which to push metrics. + management.metrics.export.prometheus.pushgateway.shutdown-operation= # Operation that should be performed on shutdown (none, push, delete). management.metrics.export.signalfx.access-token= # SignalFX access token. management.metrics.export.signalfx.batch-size=10000 # Number of measurements per request to use for this backend. If more measurements are found, then multiple requests will be made. management.metrics.export.signalfx.connect-timeout=1s # Connection timeout for requests to this backend.