Add Prometheus push gateway support

Add support for Prometheus push gateway so that short lived processes
(for example batch jobs) can still submit metrics to Prometheus.

Closes gh-14353
pull/14707/head
David J. M. Karlsen 6 years ago committed by Phillip Webb
parent 35752a54d2
commit 4e71981f77

@ -147,6 +147,11 @@
<artifactId>micrometer-registry-prometheus</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_pushgateway</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-signalfx</artifactId>

@ -16,10 +16,20 @@
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 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;
@ -36,6 +46,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
/**
* {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Prometheus.
@ -86,4 +97,113 @@ public class PrometheusMetricsExportAutoConfiguration {
}
/**
* Configuration for <a href="https://github.com/prometheus/pushgateway">Prometheus
* Pushgateway</a>.
*
* @author David J. M. Karlsen
*/
@Configuration
@ConditionalOnClass(PushGateway.class)
@ConditionalOnProperty(prefix = "management.metrics.export.prometheus.pushgateway", name = "enabled")
public static class PrometheusPushGatewayConfiguration {
@Bean
public PushGatewayHandler pushGatewayHandler(CollectorRegistry collectorRegistry,
PrometheusProperties prometheusProperties, Environment environment) {
return new PushGatewayHandler(collectorRegistry, prometheusProperties,
environment);
}
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;
}
}
}
}

@ -17,6 +17,8 @@
package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ -36,6 +38,12 @@ public class PrometheusProperties {
*/
private boolean descriptions = true;
/**
* Configuration options for using Prometheus Pushgateway, allowing metrics to be
* pushed when they cannot be scraped.
*/
private PushgatewayProperties pushgateway = new PushgatewayProperties();
/**
* Step size (i.e. reporting frequency) to use.
*/
@ -57,4 +65,110 @@ public class PrometheusProperties {
this.step = step;
}
public PushgatewayProperties getPushgateway() {
return this.pushgateway;
}
public void setPushgateway(PushgatewayProperties pushgateway) {
this.pushgateway = pushgateway;
}
/**
* Configuration options for push-based interaction with Prometheus.
*/
public static class PushgatewayProperties {
/**
* Enable publishing via a Prometheus Pushgateway.
*/
private Boolean enabled = false;
/**
* Required host:port or ip:port of the Pushgateway.
*/
private String baseUrl = "localhost:9091";
/**
* Required identifier for this application instance.
*/
private String job;
/**
* Frequency with which to push metrics to Pushgateway.
*/
private Duration pushRate = Duration.ofMinutes(1);
/**
* Push metrics right before shut-down. Mostly useful for batch jobs.
*/
private boolean pushOnShutdown = true;
/**
* Delete metrics from Pushgateway when application is shut-down.
*/
private boolean deleteOnShutdown = true;
/**
* Used to group metrics in pushgateway. A common example is setting
*/
private Map<String, String> groupingKeys = new HashMap<>();
public Boolean getEnabled() {
return this.enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public String getBaseUrl() {
return this.baseUrl;
}
public void setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
}
public String getJob() {
return this.job;
}
public void setJob(String job) {
this.job = job;
}
public Duration getPushRate() {
return this.pushRate;
}
public void setPushRate(Duration pushRate) {
this.pushRate = pushRate;
}
public boolean isPushOnShutdown() {
return this.pushOnShutdown;
}
public void setPushOnShutdown(boolean pushOnShutdown) {
this.pushOnShutdown = pushOnShutdown;
}
public boolean isDeleteOnShutdown() {
return this.deleteOnShutdown;
}
public void setDeleteOnShutdown(boolean deleteOnShutdown) {
this.deleteOnShutdown = deleteOnShutdown;
}
public Map<String, String> getGroupingKeys() {
return this.groupingKeys;
}
public void setGroupingKeys(Map<String, String> groupingKeys) {
this.groupingKeys = groupingKeys;
}
}
}

@ -22,6 +22,7 @@ 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.PrometheusScrapeEndpoint;
import org.springframework.boot.autoconfigure.AutoConfigurations;
@ -128,6 +129,18 @@ public class PrometheusMetricsExportAutoConfigurationTests {
.hasSingleBean(PrometheusScrapeEndpoint.class));
}
@Test
public void withPushGatewayEnabled() {
this.contextRunner
.withConfiguration(
AutoConfigurations.of(ManagementContextAutoConfiguration.class))
.withPropertyValues(
"management.metrics.export.prometheus.pushgateway.enabled=true")
.withUserConfiguration(BaseConfiguration.class)
.run((context) -> assertThat(context).hasSingleBean(
PrometheusPushGatewayConfiguration.PushGatewayHandler.class));
}
@Configuration
static class BaseConfiguration {

@ -143,6 +143,8 @@
<nio-multipart-parser.version>1.1.0</nio-multipart-parser.version>
<pooled-jms-version>1.0.3</pooled-jms-version>
<postgresql.version>42.2.5</postgresql.version>
<!-- need to take care that this version harmonizes with micrometer ones -->
<prometheus-pushgateway.version>0.5.0</prometheus-pushgateway.version>
<quartz.version>2.3.0</quartz.version>
<querydsl.version>4.2.1</querydsl.version>
<rabbit-amqp-client.version>5.4.1</rabbit-amqp-client.version>
@ -989,6 +991,11 @@
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>${netty-tcnative.version}</version>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
<artifactId>simpleclient_pushgateway</artifactId>
<version>${prometheus-pushgateway.version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>

Loading…
Cancel
Save