From 3e865ed7ab53cf00827c79f81b87771b69e5b65e Mon Sep 17 00:00:00 2001
From: "huan.huynh" <1612858@student.hcmus.edu.vn>
Date: Sat, 1 Nov 2025 16:25:04 +0700
Subject: [PATCH 1/3] feat: Add readTimeout and conenctionTimeout as
configurable parameters
Signed-off-by: huan.huynh <1612858@student.hcmus.edu.vn>
---
.../pom.xml | 6 ++
.../exporter/pushgateway/PushGateway.java | 43 ++++++++++-
.../pushgateway/PushGatewayTimeoutTest.java | 75 +++++++++++++++++++
3 files changed, 120 insertions(+), 4 deletions(-)
create mode 100644 prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTimeoutTest.java
diff --git a/prometheus-metrics-exporter-pushgateway/pom.xml b/prometheus-metrics-exporter-pushgateway/pom.xml
index 6d59b2b4d..350cb8ddc 100644
--- a/prometheus-metrics-exporter-pushgateway/pom.xml
+++ b/prometheus-metrics-exporter-pushgateway/pom.xml
@@ -39,5 +39,11 @@
${project.version}
test
+
+ com.squareup.okhttp3
+ mockwebserver
+ 5.3.0
+ test
+
diff --git a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java
index 643e0aeca..79e7686ce 100644
--- a/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java
+++ b/prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java
@@ -32,6 +32,7 @@
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
+import java.util.Optional;
import javax.annotation.Nullable;
/**
@@ -89,6 +90,8 @@ public class PushGateway {
private final PrometheusRegistry registry;
private final HttpConnectionFactory connectionFactory;
private final EscapingScheme escapingScheme;
+ private final Integer connectionTimeout;
+ private final Integer readTimeout;
private PushGateway(
PrometheusRegistry registry,
@@ -97,13 +100,17 @@ private PushGateway(
HttpConnectionFactory connectionFactory,
Map requestHeaders,
boolean prometheusTimestampsInMs,
- EscapingScheme escapingScheme) {
+ EscapingScheme escapingScheme,
+ @Nullable Integer connectionTimeout,
+ @Nullable Integer readTimeout) {
this.registry = registry;
this.url = url;
this.requestHeaders = Collections.unmodifiableMap(new HashMap<>(requestHeaders));
this.connectionFactory = connectionFactory;
this.prometheusTimestampsInMs = prometheusTimestampsInMs;
this.escapingScheme = escapingScheme;
+ this.connectionTimeout = Optional.ofNullable(connectionTimeout).orElse(10 * MILLISECONDS_PER_SECOND);
+ this.readTimeout = Optional.ofNullable(readTimeout).orElse(10 * MILLISECONDS_PER_SECOND);
writer = getWriter(format);
if (!writer.isAvailable()) {
throw new RuntimeException(writer.getClass() + " is not available");
@@ -206,8 +213,8 @@ private void doRequest(@Nullable PrometheusRegistry registry, String method) thr
}
connection.setRequestMethod(method);
- connection.setConnectTimeout(10 * MILLISECONDS_PER_SECOND);
- connection.setReadTimeout(10 * MILLISECONDS_PER_SECOND);
+ connection.setConnectTimeout(this.connectionTimeout);
+ connection.setReadTimeout(this.readTimeout);
connection.connect();
try {
@@ -277,6 +284,8 @@ public static class Builder {
@Nullable private String address;
@Nullable private Scheme scheme;
@Nullable private String job;
+ @Nullable private Integer connectionTimeout;
+ @Nullable private Integer readTimeout;
private boolean prometheusTimestampsInMs;
private final Map requestHeaders = new HashMap<>();
private PrometheusRegistry registry = PrometheusRegistry.defaultRegistry;
@@ -395,6 +404,30 @@ public Builder prometheusTimestampsInMs(boolean prometheusTimestampsInMs) {
return this;
}
+ /**
+ * Specify the connection timeout (in milliseconds) for HTTP connections to the PushGateway.
+ * Default is {@code 10000} (10 seconds).
+ *
+ * @param connectionTimeout timeout value in milliseconds
+ * @return this {@link Builder} instance
+ */
+ public Builder connectionTimeout(Integer connectionTimeout) {
+ this.connectionTimeout = connectionTimeout;
+ return this;
+ }
+
+ /**
+ * Specify the read timeout (in milliseconds) for reading the response from the PushGateway.
+ * Default is {@code 10000} (10 seconds).
+ *
+ * @param readTimeout timeout value in milliseconds
+ * @return this {@link Builder} instance
+ */
+ public Builder readTimeout(Integer readTimeout) {
+ this.readTimeout = readTimeout;
+ return this;
+ }
+
private boolean getPrometheusTimestampsInMs() {
// accept either to opt in to timestamps in milliseconds
return config.getExporterProperties().getPrometheusTimestampsInMs()
@@ -496,7 +529,9 @@ public PushGateway build() {
connectionFactory,
requestHeaders,
getPrometheusTimestampsInMs(),
- getEscapingScheme(properties));
+ getEscapingScheme(properties),
+ connectionTimeout,
+ readTimeout);
} catch (MalformedURLException e) {
throw new PrometheusPropertiesException(
address + ": Invalid address. Expecting :");
diff --git a/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTimeoutTest.java b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTimeoutTest.java
new file mode 100644
index 000000000..ee7540ff1
--- /dev/null
+++ b/prometheus-metrics-exporter-pushgateway/src/test/java/io/prometheus/metrics/exporter/pushgateway/PushGatewayTimeoutTest.java
@@ -0,0 +1,75 @@
+package io.prometheus.metrics.exporter.pushgateway;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.concurrent.TimeUnit;
+
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class PushGatewayTimeoutTest {
+
+ private MockWebServer mockWebServer;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ mockWebServer = new MockWebServer();
+ mockWebServer.start();
+ }
+
+ @AfterEach
+ void tearDown() throws Exception {
+ mockWebServer.shutdown();
+ }
+
+ @Test
+ void connectionTimeoutIsEnforced() {
+ // Simulate server that never accepts connection: by binding but delaying accept.
+ // Since MockWebServer always accepts connection immediately, we instead use a short connect timeout and a response delay.
+ int shortConnectTimeoutMillis = 10;
+ int readTimeoutMillis = 1000;
+
+ PushGateway pushGateway = PushGateway.builder()
+ .connectionTimeout(shortConnectTimeoutMillis)
+ .readTimeout(readTimeoutMillis)
+ .build();
+
+ // Enqueue a response that delays sending headers to simulate slow connection
+ mockWebServer.enqueue(new MockResponse()
+ .setBody("ok")
+ .setBodyDelay(5, TimeUnit.SECONDS)); // very long delay
+
+ String url = mockWebServer.url("/").toString();
+
+ Exception thrown = assertThrows(Exception.class, pushGateway::pushAdd);
+
+ assertTrue(thrown.getMessage().contains("connect"), "Expected a connection‐timeout or connect error");
+ }
+
+ @Test
+ void readTimeoutIsEnforced() {
+ int connectTimeoutMillis = 1000;
+ int shortReadTimeoutMillis = 10;
+
+ PushGateway pushGateway = PushGateway.builder()
+ .connectionTimeout(connectTimeoutMillis)
+ .readTimeout(shortReadTimeoutMillis)
+ .build();
+
+ // Enqueue a response that sends headers but delays body
+ mockWebServer.enqueue(new MockResponse()
+ .setHeadersDelay(0, TimeUnit.SECONDS)
+ .setBodyDelay(5, TimeUnit.SECONDS)
+ .setBody("ok"));
+
+ String url = mockWebServer.url("/").toString();
+
+ Exception thrown = assertThrows(Exception.class, pushGateway::pushAdd);
+
+ assertTrue(thrown.getMessage().contains("read") || thrown.getMessage().contains("timeout"),
+ "Expected a read‐timeout error");
+ }
+}
From 72e46f71bcdb476c8473614388361f179ec08dfa Mon Sep 17 00:00:00 2001
From: Gregor Zeitlinger
Date: Mon, 3 Nov 2025 17:04:37 +0100
Subject: [PATCH 2/3] read properties from file as well
Signed-off-by: Gregor Zeitlinger
Signed-off-by: huan.huynh <1612858@student.hcmus.edu.vn>
---
.../config/ExporterPushgatewayProperties.java | 42 +++++++++++-
.../io/prometheus/metrics/config/Util.java | 27 ++++++--
.../ExporterPushgatewayPropertiesTest.java | 5 ++
.../prometheus/metrics/config/UtilTest.java | 50 +++++++++++++++
.../exporter/pushgateway/PushGateway.java | 64 ++++++++++++-------
.../pushgateway/PushGatewayTimeoutTest.java | 53 ++++++++-------
6 files changed, 185 insertions(+), 56 deletions(-)
create mode 100644 prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/UtilTest.java
diff --git a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java
index 9d7b380d1..bd53f8b76 100644
--- a/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java
+++ b/prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java
@@ -1,5 +1,6 @@
package io.prometheus.metrics.config;
+import java.time.Duration;
import java.util.Map;
import javax.annotation.Nullable;
@@ -9,21 +10,29 @@ public class ExporterPushgatewayProperties {
private static final String JOB = "job";
private static final String SCHEME = "scheme";
private static final String ESCAPING_SCHEME = "escapingScheme";
+ private static final String READ_TIMEOUT = "readTimeoutSeconds";
+ private static final String CONNECT_TIMEOUT = "connectTimeoutSeconds";
private static final String PREFIX = "io.prometheus.exporter.pushgateway";
@Nullable private final String scheme;
@Nullable private final String address;
@Nullable private final String job;
@Nullable private final EscapingScheme escapingScheme;
+ @Nullable private final Duration connectTimeout;
+ @Nullable private final Duration readTimeout;
private ExporterPushgatewayProperties(
@Nullable String address,
@Nullable String job,
@Nullable String scheme,
- @Nullable EscapingScheme escapingScheme) {
+ @Nullable EscapingScheme escapingScheme,
+ @Nullable Duration connectTimeout,
+ @Nullable Duration readTimeout) {
this.address = address;
this.job = job;
this.scheme = scheme;
this.escapingScheme = escapingScheme;
+ this.connectTimeout = connectTimeout;
+ this.readTimeout = readTimeout;
}
/** Address of the Pushgateway in the form {@code host:port}. Default is {@code localhost:9091} */
@@ -56,6 +65,18 @@ public EscapingScheme getEscapingScheme() {
return escapingScheme;
}
+ /** Connection timeout for connections to the Pushgateway. */
+ @Nullable
+ public Duration getConnectTimeout() {
+ return connectTimeout;
+ }
+
+ /** Read timeout for connections to the Pushgateway. */
+ @Nullable
+ public Duration getReadTimeout() {
+ return readTimeout;
+ }
+
/**
* Note that this will remove entries from {@code properties}. This is because we want to know if
* there are unused properties remaining after all properties have been loaded.
@@ -66,6 +87,8 @@ static ExporterPushgatewayProperties load(Map