Skip to content

Commit 4e71981

Browse files
davidkarlsenphilwebb
authored andcommitted
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
1 parent 35752a5 commit 4e71981

File tree

5 files changed

+259
-0
lines changed

5 files changed

+259
-0
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@
147147
<artifactId>micrometer-registry-prometheus</artifactId>
148148
<optional>true</optional>
149149
</dependency>
150+
<dependency>
151+
<groupId>io.prometheus</groupId>
152+
<artifactId>simpleclient_pushgateway</artifactId>
153+
<optional>true</optional>
154+
</dependency>
150155
<dependency>
151156
<groupId>io.micrometer</groupId>
152157
<artifactId>micrometer-registry-signalfx</artifactId>

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfiguration.java

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,20 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus;
1818

19+
import java.net.UnknownHostException;
20+
import java.util.concurrent.Executors;
21+
import java.util.concurrent.ScheduledExecutorService;
22+
import java.util.concurrent.TimeUnit;
23+
24+
import javax.annotation.PreDestroy;
25+
1926
import io.micrometer.core.instrument.Clock;
2027
import io.micrometer.prometheus.PrometheusConfig;
2128
import io.micrometer.prometheus.PrometheusMeterRegistry;
2229
import io.prometheus.client.CollectorRegistry;
30+
import io.prometheus.client.exporter.PushGateway;
31+
import org.slf4j.Logger;
32+
import org.slf4j.LoggerFactory;
2333

2434
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
2535
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
@@ -36,6 +46,7 @@
3646
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3747
import org.springframework.context.annotation.Bean;
3848
import org.springframework.context.annotation.Configuration;
49+
import org.springframework.core.env.Environment;
3950

4051
/**
4152
* {@link EnableAutoConfiguration Auto-configuration} for exporting metrics to Prometheus.
@@ -86,4 +97,113 @@ public PrometheusScrapeEndpoint prometheusEndpoint(
8697

8798
}
8899

100+
/**
101+
* Configuration for <a href="https://github.com/prometheus/pushgateway">Prometheus
102+
* Pushgateway</a>.
103+
*
104+
* @author David J. M. Karlsen
105+
*/
106+
@Configuration
107+
@ConditionalOnClass(PushGateway.class)
108+
@ConditionalOnProperty(prefix = "management.metrics.export.prometheus.pushgateway", name = "enabled")
109+
public static class PrometheusPushGatewayConfiguration {
110+
111+
@Bean
112+
public PushGatewayHandler pushGatewayHandler(CollectorRegistry collectorRegistry,
113+
PrometheusProperties prometheusProperties, Environment environment) {
114+
return new PushGatewayHandler(collectorRegistry, prometheusProperties,
115+
environment);
116+
}
117+
118+
static class PushGatewayHandler {
119+
120+
private final Logger logger = LoggerFactory
121+
.getLogger(PrometheusPushGatewayConfiguration.class);
122+
123+
private final CollectorRegistry collectorRegistry;
124+
125+
private final PrometheusProperties.PushgatewayProperties pushgatewayProperties;
126+
127+
private final PushGateway pushGateway;
128+
129+
private final Environment environment;
130+
131+
private final ScheduledExecutorService executorService;
132+
133+
PushGatewayHandler(CollectorRegistry collectorRegistry,
134+
PrometheusProperties prometheusProperties, Environment environment) {
135+
this.collectorRegistry = collectorRegistry;
136+
this.pushgatewayProperties = prometheusProperties.getPushgateway();
137+
this.pushGateway = new PushGateway(
138+
this.pushgatewayProperties.getBaseUrl());
139+
this.environment = environment;
140+
this.executorService = Executors.newSingleThreadScheduledExecutor((r) -> {
141+
Thread thread = new Thread(r);
142+
thread.setDaemon(true);
143+
thread.setName("micrometer-pushgateway");
144+
return thread;
145+
});
146+
this.executorService.scheduleAtFixedRate(this::push, 0,
147+
this.pushgatewayProperties.getPushRate().toMillis(),
148+
TimeUnit.MILLISECONDS);
149+
}
150+
151+
void push() {
152+
try {
153+
this.pushGateway.pushAdd(this.collectorRegistry, getJobName(),
154+
this.pushgatewayProperties.getGroupingKeys());
155+
}
156+
catch (UnknownHostException ex) {
157+
this.logger.error("Unable to locate host '"
158+
+ this.pushgatewayProperties.getBaseUrl()
159+
+ "'. No longer attempting metrics publication to this host");
160+
this.executorService.shutdown();
161+
}
162+
catch (Throwable throwable) {
163+
this.logger.error("Unable to push metrics to Prometheus Pushgateway",
164+
throwable);
165+
}
166+
}
167+
168+
@PreDestroy
169+
void shutdown() {
170+
this.executorService.shutdown();
171+
if (this.pushgatewayProperties.isPushOnShutdown()) {
172+
push();
173+
}
174+
if (this.pushgatewayProperties.isDeleteOnShutdown()) {
175+
delete();
176+
}
177+
}
178+
179+
private void delete() {
180+
try {
181+
this.pushGateway.delete(getJobName(),
182+
this.pushgatewayProperties.getGroupingKeys());
183+
}
184+
catch (Throwable throwable) {
185+
this.logger.error(
186+
"Unable to delete metrics from Prometheus Pushgateway",
187+
throwable);
188+
}
189+
}
190+
191+
private String getJobName() {
192+
String job = this.pushgatewayProperties.getJob();
193+
if (job == null) {
194+
job = this.environment.getProperty("spring.application.name");
195+
}
196+
if (job == null) {
197+
// There's a history of Prometheus spring integration defaulting the
198+
// getJobName name to "spring" from when
199+
// Prometheus integration didn't exist in Spring itself.
200+
job = "spring";
201+
}
202+
return job;
203+
}
204+
205+
}
206+
207+
}
208+
89209
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusProperties.java

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus;
1818

1919
import java.time.Duration;
20+
import java.util.HashMap;
21+
import java.util.Map;
2022

2123
import org.springframework.boot.context.properties.ConfigurationProperties;
2224

@@ -36,6 +38,12 @@ public class PrometheusProperties {
3638
*/
3739
private boolean descriptions = true;
3840

41+
/**
42+
* Configuration options for using Prometheus Pushgateway, allowing metrics to be
43+
* pushed when they cannot be scraped.
44+
*/
45+
private PushgatewayProperties pushgateway = new PushgatewayProperties();
46+
3947
/**
4048
* Step size (i.e. reporting frequency) to use.
4149
*/
@@ -57,4 +65,110 @@ public void setStep(Duration step) {
5765
this.step = step;
5866
}
5967

68+
public PushgatewayProperties getPushgateway() {
69+
return this.pushgateway;
70+
}
71+
72+
public void setPushgateway(PushgatewayProperties pushgateway) {
73+
this.pushgateway = pushgateway;
74+
}
75+
76+
/**
77+
* Configuration options for push-based interaction with Prometheus.
78+
*/
79+
public static class PushgatewayProperties {
80+
81+
/**
82+
* Enable publishing via a Prometheus Pushgateway.
83+
*/
84+
private Boolean enabled = false;
85+
86+
/**
87+
* Required host:port or ip:port of the Pushgateway.
88+
*/
89+
private String baseUrl = "localhost:9091";
90+
91+
/**
92+
* Required identifier for this application instance.
93+
*/
94+
private String job;
95+
96+
/**
97+
* Frequency with which to push metrics to Pushgateway.
98+
*/
99+
private Duration pushRate = Duration.ofMinutes(1);
100+
101+
/**
102+
* Push metrics right before shut-down. Mostly useful for batch jobs.
103+
*/
104+
private boolean pushOnShutdown = true;
105+
106+
/**
107+
* Delete metrics from Pushgateway when application is shut-down.
108+
*/
109+
private boolean deleteOnShutdown = true;
110+
111+
/**
112+
* Used to group metrics in pushgateway. A common example is setting
113+
*/
114+
private Map<String, String> groupingKeys = new HashMap<>();
115+
116+
public Boolean getEnabled() {
117+
return this.enabled;
118+
}
119+
120+
public void setEnabled(Boolean enabled) {
121+
this.enabled = enabled;
122+
}
123+
124+
public String getBaseUrl() {
125+
return this.baseUrl;
126+
}
127+
128+
public void setBaseUrl(String baseUrl) {
129+
this.baseUrl = baseUrl;
130+
}
131+
132+
public String getJob() {
133+
return this.job;
134+
}
135+
136+
public void setJob(String job) {
137+
this.job = job;
138+
}
139+
140+
public Duration getPushRate() {
141+
return this.pushRate;
142+
}
143+
144+
public void setPushRate(Duration pushRate) {
145+
this.pushRate = pushRate;
146+
}
147+
148+
public boolean isPushOnShutdown() {
149+
return this.pushOnShutdown;
150+
}
151+
152+
public void setPushOnShutdown(boolean pushOnShutdown) {
153+
this.pushOnShutdown = pushOnShutdown;
154+
}
155+
156+
public boolean isDeleteOnShutdown() {
157+
return this.deleteOnShutdown;
158+
}
159+
160+
public void setDeleteOnShutdown(boolean deleteOnShutdown) {
161+
this.deleteOnShutdown = deleteOnShutdown;
162+
}
163+
164+
public Map<String, String> getGroupingKeys() {
165+
return this.groupingKeys;
166+
}
167+
168+
public void setGroupingKeys(Map<String, String> groupingKeys) {
169+
this.groupingKeys = groupingKeys;
170+
}
171+
172+
}
173+
60174
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/export/prometheus/PrometheusMetricsExportAutoConfigurationTests.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.prometheus.client.CollectorRegistry;
2323
import org.junit.Test;
2424

25+
import org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus.PrometheusMetricsExportAutoConfiguration.PrometheusPushGatewayConfiguration;
2526
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration;
2627
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint;
2728
import org.springframework.boot.autoconfigure.AutoConfigurations;
@@ -128,6 +129,18 @@ public void allowsCustomScrapeEndpointToBeUsed() {
128129
.hasSingleBean(PrometheusScrapeEndpoint.class));
129130
}
130131

132+
@Test
133+
public void withPushGatewayEnabled() {
134+
this.contextRunner
135+
.withConfiguration(
136+
AutoConfigurations.of(ManagementContextAutoConfiguration.class))
137+
.withPropertyValues(
138+
"management.metrics.export.prometheus.pushgateway.enabled=true")
139+
.withUserConfiguration(BaseConfiguration.class)
140+
.run((context) -> assertThat(context).hasSingleBean(
141+
PrometheusPushGatewayConfiguration.PushGatewayHandler.class));
142+
}
143+
131144
@Configuration
132145
static class BaseConfiguration {
133146

spring-boot-project/spring-boot-dependencies/pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@
143143
<nio-multipart-parser.version>1.1.0</nio-multipart-parser.version>
144144
<pooled-jms-version>1.0.3</pooled-jms-version>
145145
<postgresql.version>42.2.5</postgresql.version>
146+
<!-- need to take care that this version harmonizes with micrometer ones -->
147+
<prometheus-pushgateway.version>0.5.0</prometheus-pushgateway.version>
146148
<quartz.version>2.3.0</quartz.version>
147149
<querydsl.version>4.2.1</querydsl.version>
148150
<rabbit-amqp-client.version>5.4.1</rabbit-amqp-client.version>
@@ -989,6 +991,11 @@
989991
<artifactId>netty-tcnative-boringssl-static</artifactId>
990992
<version>${netty-tcnative.version}</version>
991993
</dependency>
994+
<dependency>
995+
<groupId>io.prometheus</groupId>
996+
<artifactId>simpleclient_pushgateway</artifactId>
997+
<version>${prometheus-pushgateway.version}</version>
998+
</dependency>
992999
<dependency>
9931000
<groupId>io.projectreactor</groupId>
9941001
<artifactId>reactor-bom</artifactId>

0 commit comments

Comments
 (0)