Skip to content

Commit ff52916

Browse files
hexmindRobWin
authored andcommitted
Issue ReactiveX#531: Added Prometheus metrics to TimeLimiter (ReactiveX#600)
1 parent 1319d04 commit ff52916

File tree

6 files changed

+252
-36
lines changed

6 files changed

+252
-36
lines changed

resilience4j-metrics/src/main/java/io/github/resilience4j/metrics/TimeLimiterMetrics.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@
3030
import com.codahale.metrics.MetricSet;
3131

3232
import static com.codahale.metrics.MetricRegistry.name;
33-
import static io.github.resilience4j.timelimiter.utils.MetricNames.DEFAULT_PREFIX;
34-
import static io.github.resilience4j.timelimiter.utils.MetricNames.FAILED;
35-
import static io.github.resilience4j.timelimiter.utils.MetricNames.SUCCESSFUL;
36-
import static io.github.resilience4j.timelimiter.utils.MetricNames.TIMEOUT;
3733
import static java.util.Objects.requireNonNull;
3834

3935
/**
4036
* An adapter which exports TimeLimiter's events as Dropwizard Metrics.
4137
*/
4238
public class TimeLimiterMetrics implements MetricSet {
4339

40+
private static final String DEFAULT_PREFIX = "resilience4j.timelimiter";
41+
private static final String SUCCESSFUL = "successful";
42+
private static final String FAILED = "failed";
43+
private static final String TIMEOUT = "timeout";
4444
private static final String PREFIX_NULL = "Prefix must not be null";
4545
private static final String ITERABLE_NULL = "TimeLimiters iterable must not be null";
4646

resilience4j-prometheus/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ dependencies {
22
compile (libraries.prometheus_simpleclient)
33
compileOnly project(':resilience4j-circuitbreaker')
44
compileOnly project(':resilience4j-ratelimiter')
5+
compileOnly project(':resilience4j-timelimiter')
56
compileOnly project(':resilience4j-retry')
67
compileOnly project(':resilience4j-bulkhead')
78
testCompile project(':resilience4j-circuitbreaker')
89
testCompile project(':resilience4j-ratelimiter')
10+
testCompile project(':resilience4j-timelimiter')
911
testCompile project(':resilience4j-bulkhead')
1012
testCompile project(':resilience4j-retry')
1113
testCompile (libraries.prometheus_simpleclient)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/*
2+
* Copyright 2019 authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.resilience4j.prometheus.collectors;
17+
18+
import io.github.resilience4j.timelimiter.TimeLimiter;
19+
import io.github.resilience4j.timelimiter.TimeLimiterRegistry;
20+
import io.prometheus.client.Collector;
21+
import io.prometheus.client.CollectorRegistry;
22+
import io.prometheus.client.Counter;
23+
24+
import java.util.Collections;
25+
import java.util.List;
26+
27+
import static java.util.Objects.requireNonNull;
28+
29+
/**
30+
* Collects TimeLimiter exposed events.
31+
*/
32+
public class TimeLimiterMetricsCollector extends Collector {
33+
34+
static final String KIND_SUCCESSFUL = "successful";
35+
static final String KIND_FAILED = "failed";
36+
static final String KIND_TIMEOUT = "timeout";
37+
38+
/**
39+
* Creates a new collector with custom metric names and
40+
* using given {@code supplier} as source of time limiters.
41+
*
42+
* @param names the custom metric names
43+
* @param timeLimiterRegistry the source of time limiters
44+
*/
45+
public static TimeLimiterMetricsCollector ofTimeLimiterRegistry(TimeLimiterMetricsCollector.MetricNames names, TimeLimiterRegistry timeLimiterRegistry) {
46+
return new TimeLimiterMetricsCollector(names, timeLimiterRegistry);
47+
}
48+
49+
/**
50+
* Creates a new collector using given {@code registry} as source of time limiters.
51+
*
52+
* @param timeLimiterRegistry the source of time limiters
53+
*/
54+
public static TimeLimiterMetricsCollector ofTimeLimiterRegistry(TimeLimiterRegistry timeLimiterRegistry) {
55+
return new TimeLimiterMetricsCollector(TimeLimiterMetricsCollector.MetricNames.ofDefaults(), timeLimiterRegistry);
56+
}
57+
58+
private final MetricNames names;
59+
private final TimeLimiterRegistry timeLimiterRegistry;
60+
private final CollectorRegistry collectorRegistry = new CollectorRegistry(true);
61+
private final Counter callsCounter;
62+
63+
private TimeLimiterMetricsCollector(MetricNames names, TimeLimiterRegistry timeLimiterRegistry) {
64+
this.names = requireNonNull(names);
65+
this.timeLimiterRegistry = requireNonNull(timeLimiterRegistry);
66+
this.callsCounter = Counter.build(names.getCallsMetricName(),
67+
"Total number of calls by kind")
68+
.labelNames("name", "kind")
69+
.create().register(collectorRegistry);
70+
71+
this.timeLimiterRegistry.getAllTimeLimiters()
72+
.forEach(this::addMetrics);
73+
this.timeLimiterRegistry.getEventPublisher()
74+
.onEntryAdded(event -> addMetrics(event.getAddedEntry()));
75+
}
76+
77+
private void addMetrics(TimeLimiter timeLimiter) {
78+
String name = timeLimiter.getName();
79+
timeLimiter.getEventPublisher()
80+
.onSuccess(event -> callsCounter.labels(name, KIND_SUCCESSFUL).inc())
81+
.onError(event -> callsCounter.labels(name, KIND_FAILED).inc())
82+
.onTimeout(event -> callsCounter.labels(name, KIND_TIMEOUT).inc());
83+
}
84+
85+
@Override
86+
public List<MetricFamilySamples> collect() {
87+
return Collections.list(collectorRegistry.metricFamilySamples());
88+
}
89+
90+
/**
91+
* Defines possible configuration for metric names.
92+
*/
93+
public static class MetricNames {
94+
95+
public static final String DEFAULT_CALLS_METRIC_NAME = "resilience4j_timelimiter_calls";
96+
97+
/**
98+
* Returns a builder for creating custom metric names.
99+
* Note that names have default values, so only desired metrics can be renamed.
100+
*/
101+
public static Builder custom() {
102+
return new Builder();
103+
}
104+
105+
/**
106+
* Returns default metric names.
107+
*/
108+
public static MetricNames ofDefaults() {
109+
return new MetricNames();
110+
}
111+
112+
private String callsMetricName = DEFAULT_CALLS_METRIC_NAME;
113+
114+
/**
115+
* Returns the metric name for calls, defaults to {@value DEFAULT_CALLS_METRIC_NAME}.
116+
*/
117+
public String getCallsMetricName() {
118+
return callsMetricName;
119+
}
120+
121+
/**
122+
* Helps building custom instance of {@link MetricNames}.
123+
*/
124+
public static class Builder {
125+
126+
private final MetricNames metricNames = new MetricNames();
127+
128+
/**
129+
* Overrides the default metric name {@value MetricNames#DEFAULT_CALLS_METRIC_NAME} with a given one.
130+
*/
131+
public Builder callsMetricName(String callsMetricName) {
132+
metricNames.callsMetricName = requireNonNull(callsMetricName);
133+
return this;
134+
}
135+
136+
/**
137+
* Builds {@link MetricNames} instance.
138+
*/
139+
public MetricNames build() {
140+
return metricNames;
141+
}
142+
}
143+
}
144+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2019 authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.resilience4j.prometheus.collectors;
17+
18+
import io.github.resilience4j.timelimiter.TimeLimiter;
19+
import io.github.resilience4j.timelimiter.TimeLimiterRegistry;
20+
import io.prometheus.client.CollectorRegistry;
21+
22+
import java.util.concurrent.TimeoutException;
23+
24+
import org.junit.Before;
25+
import org.junit.Test;
26+
27+
import static io.github.resilience4j.prometheus.collectors.TimeLimiterMetricsCollector.KIND_FAILED;
28+
import static io.github.resilience4j.prometheus.collectors.TimeLimiterMetricsCollector.KIND_SUCCESSFUL;
29+
import static io.github.resilience4j.prometheus.collectors.TimeLimiterMetricsCollector.KIND_TIMEOUT;
30+
import static io.github.resilience4j.prometheus.collectors.TimeLimiterMetricsCollector.MetricNames.DEFAULT_CALLS_METRIC_NAME;
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
33+
public class TimeLimiterMetricsCollectorTest {
34+
35+
private CollectorRegistry registry;
36+
private TimeLimiter timeLimiter;
37+
private TimeLimiterRegistry timeLimiterRegistry;
38+
39+
@Before
40+
public void setup() {
41+
registry = new CollectorRegistry();
42+
timeLimiterRegistry = TimeLimiterRegistry.ofDefaults();
43+
timeLimiter = timeLimiterRegistry.timeLimiter("backendA");
44+
45+
TimeLimiterMetricsCollector.ofTimeLimiterRegistry(timeLimiterRegistry).register(registry);
46+
}
47+
48+
@Test
49+
public void successfulCallsReportsCorrespondingValue() {
50+
timeLimiter.onSuccess();
51+
52+
Double successfulCalls = getSampleValue(registry, DEFAULT_CALLS_METRIC_NAME, KIND_SUCCESSFUL);
53+
54+
assertThat(successfulCalls).isEqualTo(1);
55+
}
56+
57+
@Test
58+
public void failedCallsReportsCorrespondingValue() {
59+
timeLimiter.onError(new RuntimeException());
60+
61+
Double failedCalls = getSampleValue(registry, DEFAULT_CALLS_METRIC_NAME, KIND_FAILED);
62+
63+
assertThat(failedCalls).isEqualTo(1);
64+
}
65+
66+
@Test
67+
public void timeoutCallsReportsCorrespondingValue() {
68+
timeLimiter.onError(new TimeoutException());
69+
70+
Double timeoutCalls = getSampleValue(registry, DEFAULT_CALLS_METRIC_NAME, KIND_TIMEOUT);
71+
72+
assertThat(timeoutCalls).isEqualTo(1);
73+
}
74+
75+
@Test
76+
public void customMetricNamesOverrideDefaultOnes() {
77+
TimeLimiterMetricsCollector.MetricNames names = TimeLimiterMetricsCollector.MetricNames.custom()
78+
.callsMetricName("custom_calls")
79+
.build();
80+
CollectorRegistry customRegistry = new CollectorRegistry();
81+
TimeLimiterMetricsCollector.ofTimeLimiterRegistry(names, timeLimiterRegistry).register(customRegistry);
82+
timeLimiter.onSuccess();
83+
timeLimiter.onError(new RuntimeException());
84+
timeLimiter.onError(new TimeoutException());
85+
86+
Double successfulCalls = getSampleValue(customRegistry, "custom_calls", KIND_SUCCESSFUL);
87+
Double failedCalls = getSampleValue(customRegistry, "custom_calls", KIND_FAILED);
88+
Double timeoutCalls = getSampleValue(customRegistry, "custom_calls", KIND_TIMEOUT);
89+
90+
assertThat(successfulCalls).isNotNull();
91+
assertThat(failedCalls).isNotNull();
92+
assertThat(timeoutCalls).isNotNull();
93+
}
94+
95+
private Double getSampleValue(CollectorRegistry collectorRegistry, String metricName, String metricKind) {
96+
return collectorRegistry.getSampleValue(
97+
metricName,
98+
new String[]{"name", "kind"},
99+
new String[]{timeLimiter.getName(), metricKind}
100+
);
101+
}
102+
}

resilience4j-timelimiter/src/main/java/io/github/resilience4j/timelimiter/utils/MetricNames.java

Lines changed: 0 additions & 8 deletions
This file was deleted.

resilience4j-timelimiter/src/main/java/io/github/resilience4j/timelimiter/utils/package-info.java

Lines changed: 0 additions & 24 deletions
This file was deleted.

0 commit comments

Comments
 (0)