Skip to content

Commit e7aa3c6

Browse files
hmhuanzeitlinger
andauthored
feat: Add readTimeout and conenctionTimeout as configurable parameters (#1658)
I got the issue like below so I would like to contribute to fix it by apply configurable for `connectionTimeout` and `readTimeout`. ``` stack_trace: java.io.IOException: ailed to push metrics to the Prometheus Pushgateway on <pushgateway_address>: Read timed out Caused by: java.net.SocketTimeoutException: Read timed out ``` - AS-IS: the current connectionTimeout and readTimeout is 10s - TOBE: we can create builder with connectionTimeout and readTimeout custom value, default value is 10s --------- Signed-off-by: huan.huynh <[email protected]> Signed-off-by: Gregor Zeitlinger <[email protected]> Co-authored-by: Gregor Zeitlinger <[email protected]>
1 parent eb36967 commit e7aa3c6

File tree

5 files changed

+174
-15
lines changed

5 files changed

+174
-15
lines changed

prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/ExporterPushgatewayProperties.java

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.prometheus.metrics.config;
22

3+
import java.time.Duration;
34
import java.util.Map;
45
import javax.annotation.Nullable;
56

@@ -9,21 +10,29 @@ public class ExporterPushgatewayProperties {
910
private static final String JOB = "job";
1011
private static final String SCHEME = "scheme";
1112
private static final String ESCAPING_SCHEME = "escapingScheme";
13+
private static final String READ_TIMEOUT = "readTimeoutSeconds";
14+
private static final String CONNECT_TIMEOUT = "connectTimeoutSeconds";
1215
private static final String PREFIX = "io.prometheus.exporter.pushgateway";
1316
@Nullable private final String scheme;
1417
@Nullable private final String address;
1518
@Nullable private final String job;
1619
@Nullable private final EscapingScheme escapingScheme;
20+
@Nullable private final Duration connectTimeout;
21+
@Nullable private final Duration readTimeout;
1722

1823
private ExporterPushgatewayProperties(
1924
@Nullable String address,
2025
@Nullable String job,
2126
@Nullable String scheme,
22-
@Nullable EscapingScheme escapingScheme) {
27+
@Nullable EscapingScheme escapingScheme,
28+
@Nullable Duration connectTimeout,
29+
@Nullable Duration readTimeout) {
2330
this.address = address;
2431
this.job = job;
2532
this.scheme = scheme;
2633
this.escapingScheme = escapingScheme;
34+
this.connectTimeout = connectTimeout;
35+
this.readTimeout = readTimeout;
2736
}
2837

2938
/** Address of the Pushgateway in the form {@code host:port}. Default is {@code localhost:9091} */
@@ -56,6 +65,18 @@ public EscapingScheme getEscapingScheme() {
5665
return escapingScheme;
5766
}
5867

68+
/** Connection timeout for connections to the Pushgateway. */
69+
@Nullable
70+
public Duration getConnectTimeout() {
71+
return connectTimeout;
72+
}
73+
74+
/** Read timeout for connections to the Pushgateway. */
75+
@Nullable
76+
public Duration getReadTimeout() {
77+
return readTimeout;
78+
}
79+
5980
/**
6081
* Note that this will remove entries from {@code properties}. This is because we want to know if
6182
* there are unused properties remaining after all properties have been loaded.
@@ -66,6 +87,8 @@ static ExporterPushgatewayProperties load(Map<Object, Object> properties)
6687
String job = Util.loadString(PREFIX + "." + JOB, properties);
6788
String scheme = Util.loadString(PREFIX + "." + SCHEME, properties);
6889
String escapingScheme = Util.loadString(PREFIX + "." + ESCAPING_SCHEME, properties);
90+
Duration connectTimeout = Util.loadOptionalDuration(PREFIX + "." + CONNECT_TIMEOUT, properties);
91+
Duration readTimeout = Util.loadOptionalDuration(PREFIX + "." + READ_TIMEOUT, properties);
6992

7093
if (scheme != null) {
7194
if (!scheme.equals("http") && !scheme.equals("https")) {
@@ -77,7 +100,7 @@ static ExporterPushgatewayProperties load(Map<Object, Object> properties)
77100
}
78101

79102
return new ExporterPushgatewayProperties(
80-
address, job, scheme, parseEscapingScheme(escapingScheme));
103+
address, job, scheme, parseEscapingScheme(escapingScheme), connectTimeout, readTimeout);
81104
}
82105

83106
private static @Nullable EscapingScheme parseEscapingScheme(@Nullable String scheme) {
@@ -111,6 +134,8 @@ public static class Builder {
111134
@Nullable private String job;
112135
@Nullable private String scheme;
113136
@Nullable private EscapingScheme escapingScheme;
137+
@Nullable private Duration connectTimeout;
138+
@Nullable private Duration readTimeout;
114139

115140
private Builder() {}
116141

@@ -134,8 +159,19 @@ public Builder escapingScheme(EscapingScheme escapingScheme) {
134159
return this;
135160
}
136161

162+
public Builder connectTimeout(Duration connectTimeout) {
163+
this.connectTimeout = connectTimeout;
164+
return this;
165+
}
166+
167+
public Builder readTimeout(Duration readTimeout) {
168+
this.readTimeout = readTimeout;
169+
return this;
170+
}
171+
137172
public ExporterPushgatewayProperties build() {
138-
return new ExporterPushgatewayProperties(address, job, scheme, escapingScheme);
173+
return new ExporterPushgatewayProperties(
174+
address, job, scheme, escapingScheme, connectTimeout, readTimeout);
139175
}
140176
}
141177
}

prometheus-metrics-config/src/main/java/io/prometheus/metrics/config/Util.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.prometheus.metrics.config;
22

3+
import java.time.Duration;
34
import java.util.ArrayList;
45
import java.util.Arrays;
56
import java.util.HashMap;
@@ -160,14 +161,30 @@ static Long loadLong(String name, Map<Object, Object> properties)
160161
return null;
161162
}
162163

164+
@Nullable
165+
static Duration loadOptionalDuration(String name, Map<Object, Object> properties)
166+
throws PrometheusPropertiesException {
167+
168+
Long value = loadLong(name, properties);
169+
170+
assertValue(value, t -> t >= 0, "Expecting value >= 0.", null, name);
171+
172+
if (value == null || value == 0) {
173+
return null;
174+
}
175+
return Duration.ofSeconds(value);
176+
}
177+
163178
static <T extends Number> void assertValue(
164-
@Nullable T number, Predicate<T> predicate, String message, String prefix, String name)
179+
@Nullable T number,
180+
Predicate<T> predicate,
181+
String message,
182+
@Nullable String prefix,
183+
String name)
165184
throws PrometheusPropertiesException {
166185
if (number != null && !predicate.test(number)) {
167-
String fullMessage =
168-
prefix == null
169-
? name + ": " + message
170-
: String.format("%s.%s: %s Found: %s", prefix, name, message, number);
186+
String fullKey = prefix == null ? name : prefix + "." + name;
187+
String fullMessage = String.format("%s: %s Found: %s", fullKey, message, number);
171188
throw new PrometheusPropertiesException(fullMessage);
172189
}
173190
}

prometheus-metrics-config/src/test/java/io/prometheus/metrics/config/ExporterPushgatewayPropertiesTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static org.assertj.core.api.Assertions.assertThat;
44
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
55

6+
import java.time.Duration;
67
import java.util.HashMap;
78
import java.util.Map;
89
import org.junit.jupiter.api.Test;
@@ -41,11 +42,15 @@ void builder() {
4142
.job("job")
4243
.scheme("http")
4344
.escapingScheme(EscapingScheme.DOTS_ESCAPING)
45+
.connectTimeout(Duration.ofSeconds(1))
46+
.readTimeout(Duration.ofSeconds(2))
4447
.build();
4548

4649
assertThat(properties.getAddress()).isEqualTo("http://localhost");
4750
assertThat(properties.getJob()).isEqualTo("job");
4851
assertThat(properties.getScheme()).isEqualTo("http");
4952
assertThat(properties.getEscapingScheme()).isEqualTo(EscapingScheme.DOTS_ESCAPING);
53+
assertThat(properties.getConnectTimeout()).isEqualTo(Duration.ofSeconds(1));
54+
assertThat(properties.getReadTimeout()).isEqualTo(Duration.ofSeconds(2));
5055
}
5156
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package io.prometheus.metrics.config;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
5+
6+
import java.time.Duration;
7+
import java.util.HashMap;
8+
import java.util.Map;
9+
import org.junit.jupiter.api.Test;
10+
11+
class UtilTest {
12+
@Test
13+
void loadOptionalDuration_positive() {
14+
Map<Object, Object> properties = new HashMap<>(Map.of("foo", "5"));
15+
16+
assertThat(Util.loadOptionalDuration("foo", properties)).isEqualTo(Duration.ofSeconds(5));
17+
}
18+
19+
@Test
20+
void loadOptionalDuration_zero() {
21+
Map<Object, Object> properties = new HashMap<>(Map.of("foo", "0"));
22+
23+
assertThat(Util.loadOptionalDuration("foo", properties)).isNull();
24+
}
25+
26+
@Test
27+
void loadOptionalDuration_missing() {
28+
Map<Object, Object> properties = new HashMap<>();
29+
30+
assertThat(Util.loadOptionalDuration("foo", properties)).isNull();
31+
}
32+
33+
@Test
34+
void loadOptionalDuration_negative_throws() {
35+
Map<Object, Object> properties = new HashMap<>(Map.of("foo", "-1"));
36+
37+
assertThatExceptionOfType(PrometheusPropertiesException.class)
38+
.isThrownBy(() -> Util.loadOptionalDuration("foo", properties))
39+
.withMessage("foo: Expecting value >= 0. Found: -1");
40+
}
41+
42+
@Test
43+
void loadOptionalDuration_invalidNumber_throws() {
44+
Map<Object, Object> properties = new HashMap<>(Map.of("foo", "abc"));
45+
46+
assertThatExceptionOfType(PrometheusPropertiesException.class)
47+
.isThrownBy(() -> Util.loadOptionalDuration("foo", properties))
48+
.withMessage("foo=abc: Expecting long value");
49+
}
50+
}

prometheus-metrics-exporter-pushgateway/src/main/java/io/prometheus/metrics/exporter/pushgateway/PushGateway.java

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.net.URLEncoder;
2828
import java.net.UnknownHostException;
2929
import java.nio.charset.StandardCharsets;
30+
import java.time.Duration;
3031
import java.util.Base64;
3132
import java.util.Collections;
3233
import java.util.HashMap;
@@ -79,16 +80,15 @@
7980
* href="https://github.com/prometheus/pushgateway">https://github.com/prometheus/pushgateway</a>.
8081
*/
8182
public class PushGateway {
82-
83-
private static final int MILLISECONDS_PER_SECOND = 1000;
84-
8583
private final URL url;
8684
private final ExpositionFormatWriter writer;
8785
private final boolean prometheusTimestampsInMs;
8886
private final Map<String, String> requestHeaders;
8987
private final PrometheusRegistry registry;
9088
private final HttpConnectionFactory connectionFactory;
9189
private final EscapingScheme escapingScheme;
90+
private final Duration connectionTimeout;
91+
private final Duration readTimeout;
9292

9393
private PushGateway(
9494
PrometheusRegistry registry,
@@ -97,13 +97,17 @@ private PushGateway(
9797
HttpConnectionFactory connectionFactory,
9898
Map<String, String> requestHeaders,
9999
boolean prometheusTimestampsInMs,
100-
EscapingScheme escapingScheme) {
100+
EscapingScheme escapingScheme,
101+
Duration connectionTimeout,
102+
Duration readTimeout) {
101103
this.registry = registry;
102104
this.url = url;
103105
this.requestHeaders = Collections.unmodifiableMap(new HashMap<>(requestHeaders));
104106
this.connectionFactory = connectionFactory;
105107
this.prometheusTimestampsInMs = prometheusTimestampsInMs;
106108
this.escapingScheme = escapingScheme;
109+
this.connectionTimeout = connectionTimeout;
110+
this.readTimeout = readTimeout;
107111
writer = getWriter(format);
108112
if (!writer.isAvailable()) {
109113
throw new RuntimeException(writer.getClass() + " is not available");
@@ -206,8 +210,8 @@ private void doRequest(@Nullable PrometheusRegistry registry, String method) thr
206210
}
207211
connection.setRequestMethod(method);
208212

209-
connection.setConnectTimeout(10 * MILLISECONDS_PER_SECOND);
210-
connection.setReadTimeout(10 * MILLISECONDS_PER_SECOND);
213+
connection.setConnectTimeout((int) this.connectionTimeout.toMillis());
214+
connection.setReadTimeout((int) this.readTimeout.toMillis());
211215
connection.connect();
212216

213217
try {
@@ -277,6 +281,8 @@ public static class Builder {
277281
@Nullable private String address;
278282
@Nullable private Scheme scheme;
279283
@Nullable private String job;
284+
@Nullable private Duration connectionTimeout;
285+
@Nullable private Duration readTimeout;
280286
private boolean prometheusTimestampsInMs;
281287
private final Map<String, String> requestHeaders = new HashMap<>();
282288
private PrometheusRegistry registry = PrometheusRegistry.defaultRegistry;
@@ -395,6 +401,49 @@ public Builder prometheusTimestampsInMs(boolean prometheusTimestampsInMs) {
395401
return this;
396402
}
397403

404+
/**
405+
* Specify the connection timeout for HTTP connections to the PushGateway. Default is 10
406+
* seconds.
407+
*
408+
* @param connectionTimeout timeout value
409+
* @return this {@link Builder} instance
410+
*/
411+
public Builder connectionTimeout(Duration connectionTimeout) {
412+
this.connectionTimeout = connectionTimeout;
413+
return this;
414+
}
415+
416+
private Duration getConnectionTimeout(@Nullable ExporterPushgatewayProperties properties) {
417+
if (properties != null && properties.getConnectTimeout() != null) {
418+
return properties.getConnectTimeout();
419+
} else if (this.connectionTimeout != null) {
420+
return this.connectionTimeout;
421+
} else {
422+
return Duration.ofSeconds(10);
423+
}
424+
}
425+
426+
/**
427+
* Specify the read timeout for HTTP connections to the PushGateway. Default is 10 seconds.
428+
*
429+
* @param readTimeout timeout value
430+
* @return this {@link Builder} instance
431+
*/
432+
public Builder readTimeout(Duration readTimeout) {
433+
this.readTimeout = readTimeout;
434+
return this;
435+
}
436+
437+
private Duration getReadTimeout(@Nullable ExporterPushgatewayProperties properties) {
438+
if (properties != null && properties.getReadTimeout() != null) {
439+
return properties.getReadTimeout();
440+
} else if (this.readTimeout != null) {
441+
return this.readTimeout;
442+
} else {
443+
return Duration.ofSeconds(10);
444+
}
445+
}
446+
398447
private boolean getPrometheusTimestampsInMs() {
399448
// accept either to opt in to timestamps in milliseconds
400449
return config.getExporterProperties().getPrometheusTimestampsInMs()
@@ -496,7 +545,9 @@ public PushGateway build() {
496545
connectionFactory,
497546
requestHeaders,
498547
getPrometheusTimestampsInMs(),
499-
getEscapingScheme(properties));
548+
getEscapingScheme(properties),
549+
getConnectionTimeout(properties),
550+
getReadTimeout(properties));
500551
} catch (MalformedURLException e) {
501552
throw new PrometheusPropertiesException(
502553
address + ": Invalid address. Expecting <host>:<port>");

0 commit comments

Comments
 (0)