Skip to content

Commit 750862b

Browse files
authored
Merge pull request ReactiveX#88 from resilience4j/ratelimiter_metrics
Issue ReactiveX#72: rate limiter metrics initial
2 parents 60b1a01 + e2a2a07 commit 750862b

File tree

7 files changed

+294
-28
lines changed

7 files changed

+294
-28
lines changed

resilience4j-metrics/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
dependencies {
22
compile (libraries.metrics)
33
compileOnly project(':resilience4j-circuitbreaker')
4+
compileOnly project(':resilience4j-ratelimiter')
45
testCompile project(':resilience4j-test')
56
testCompile project(':resilience4j-circuitbreaker')
7+
testCompile project(':resilience4j-ratelimiter')
68
}

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

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,48 +18,47 @@
1818
*/
1919
package io.github.resilience4j.metrics;
2020

21+
import static com.codahale.metrics.MetricRegistry.name;
22+
import static java.util.Objects.requireNonNull;
23+
2124
import com.codahale.metrics.Gauge;
2225
import com.codahale.metrics.Metric;
2326
import com.codahale.metrics.MetricRegistry;
2427
import com.codahale.metrics.MetricSet;
25-
26-
import java.util.Map;
27-
2828
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
2929
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
3030
import javaslang.collection.Array;
3131

32-
import static com.codahale.metrics.MetricRegistry.name;
33-
import static java.util.Objects.requireNonNull;
32+
import java.util.Map;
3433

3534
/**
3635
* An adapter which exports {@link CircuitBreaker.Metrics} as Dropwizard Metrics Gauges.
3736
*/
38-
public class CircuitBreakerMetrics implements MetricSet{
37+
public class CircuitBreakerMetrics implements MetricSet {
3938

40-
private final MetricRegistry metricRegistry = new MetricRegistry();
4139
private static final String DEFAULT_PREFIX = "resilience4j.circuitbreaker";
40+
private final MetricRegistry metricRegistry = new MetricRegistry();
4241

43-
private CircuitBreakerMetrics(Iterable<CircuitBreaker> circuitBreakers){
42+
private CircuitBreakerMetrics(Iterable<CircuitBreaker> circuitBreakers) {
4443
this(DEFAULT_PREFIX, circuitBreakers);
4544
}
4645

47-
private CircuitBreakerMetrics(String prefix, Iterable<CircuitBreaker> circuitBreakers){
46+
private CircuitBreakerMetrics(String prefix, Iterable<CircuitBreaker> circuitBreakers) {
4847
requireNonNull(prefix);
4948
requireNonNull(circuitBreakers);
5049
circuitBreakers.forEach(circuitBreaker -> {
51-
String name = circuitBreaker.getName();
52-
CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
50+
String name = circuitBreaker.getName();
51+
CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
5352

54-
metricRegistry.register(name(prefix, name, "successful"),
53+
metricRegistry.register(name(prefix, name, "successful"),
5554
(Gauge<Integer>) metrics::getNumberOfSuccessfulCalls);
56-
metricRegistry.register(name(prefix, name, "failed"),
55+
metricRegistry.register(name(prefix, name, "failed"),
5756
(Gauge<Integer>) metrics::getNumberOfFailedCalls);
58-
metricRegistry.register(name(prefix, name, "not_permitted"),
57+
metricRegistry.register(name(prefix, name, "not_permitted"),
5958
(Gauge<Long>) metrics::getNumberOfNotPermittedCalls);
60-
metricRegistry.register(name(prefix, name, "buffered"),
59+
metricRegistry.register(name(prefix, name, "buffered"),
6160
(Gauge<Integer>) metrics::getNumberOfBufferedCalls);
62-
metricRegistry.register(name(prefix, name, "buffered_max"),
61+
metricRegistry.register(name(prefix, name, "buffered_max"),
6362
(Gauge<Integer>) metrics::getMaxNumberOfBufferedCalls);
6463
}
6564
);
@@ -69,7 +68,7 @@ private CircuitBreakerMetrics(String prefix, Iterable<CircuitBreaker> circuitBre
6968
* Creates a new instance CircuitBreakerMetrics {@link CircuitBreakerMetrics} with specified metrics names prefix and
7069
* a {@link CircuitBreakerRegistry} as a source.
7170
*
72-
* @param prefix the prefix of metrics names
71+
* @param prefix the prefix of metrics names
7372
* @param circuitBreakerRegistry the registry of circuit breakers
7473
*/
7574
public static CircuitBreakerMetrics ofCircuitBreakerRegistry(String prefix, CircuitBreakerRegistry circuitBreakerRegistry) {
@@ -79,7 +78,7 @@ public static CircuitBreakerMetrics ofCircuitBreakerRegistry(String prefix, Circ
7978
/**
8079
* Creates a new instance CircuitBreakerMetrics {@link CircuitBreakerMetrics} with
8180
* a {@link CircuitBreakerRegistry} as a source.
82-
81+
*
8382
* @param circuitBreakerRegistry the registry of circuit breakers
8483
*/
8584
public static CircuitBreakerMetrics ofCircuitBreakerRegistry(CircuitBreakerRegistry circuitBreakerRegistry) {
@@ -98,12 +97,12 @@ public static CircuitBreakerMetrics ofIterable(Iterable<CircuitBreaker> circuitB
9897

9998
/**
10099
* Creates a new instance CircuitBreakerMetrics {@link CircuitBreakerMetrics} with
101-
* an {@link Iterable} if circuit breakers as a source.
100+
* an {@link Iterable} of circuit breakers as a source.
102101
*
103102
* @param circuitBreakers the circuit breakers
104103
*/
105104
public static CircuitBreakerMetrics ofIterable(String prefix, Iterable<CircuitBreaker> circuitBreakers) {
106-
return new CircuitBreakerMetrics(circuitBreakers);
105+
return new CircuitBreakerMetrics(prefix, circuitBreakers);
107106
}
108107

109108

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
*
3+
* Copyright 2017: Bohdan Storozhuk
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package io.github.resilience4j.metrics;
20+
21+
import static com.codahale.metrics.MetricRegistry.name;
22+
import static java.util.Objects.requireNonNull;
23+
24+
import com.codahale.metrics.Gauge;
25+
import com.codahale.metrics.Metric;
26+
import com.codahale.metrics.MetricRegistry;
27+
import com.codahale.metrics.MetricSet;
28+
import io.github.resilience4j.ratelimiter.RateLimiter;
29+
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
30+
import javaslang.collection.Array;
31+
32+
import java.util.Map;
33+
34+
/**
35+
* An adapter which exports {@link RateLimiter.Metrics} as Dropwizard Metrics Gauges.
36+
*/
37+
public class RateLimiterMetrics implements MetricSet {
38+
39+
private static final String PREFIX_NULL = "Prefix must not be null";
40+
private static final String ITERABLE_NULL = "RateLimiters iterable must not be null";
41+
42+
private static final String DEFAULT_PREFIX = "resilience4j.ratelimiter";
43+
44+
private final MetricRegistry metricRegistry = new MetricRegistry();
45+
46+
private RateLimiterMetrics(Iterable<RateLimiter> rateLimiters) {
47+
this(DEFAULT_PREFIX, rateLimiters);
48+
}
49+
50+
private RateLimiterMetrics(String prefix, Iterable<RateLimiter> rateLimiters) {
51+
requireNonNull(prefix, PREFIX_NULL);
52+
requireNonNull(rateLimiters, ITERABLE_NULL);
53+
54+
rateLimiters.forEach(rateLimiter -> {
55+
String name = rateLimiter.getName();
56+
RateLimiter.Metrics metrics = rateLimiter.getMetrics();
57+
58+
metricRegistry.register(name(prefix, name, "number_of_waiting_threads"),
59+
(Gauge<Integer>) metrics::getNumberOfWaitingThreads);
60+
metricRegistry.register(name(prefix, name, "available_permissions"),
61+
(Gauge<Integer>) metrics::getAvailablePermissions);
62+
}
63+
);
64+
}
65+
66+
/**
67+
* Creates a new instance {@link RateLimiterMetrics} with specified metrics names prefix and
68+
* a {@link RateLimiterRegistry} as a source.
69+
*
70+
* @param prefix the prefix of metrics names
71+
* @param rateLimiterRegistry the registry of rate limiters
72+
*/
73+
public static RateLimiterMetrics ofRateLimiterRegistry(String prefix, RateLimiterRegistry rateLimiterRegistry) {
74+
return new RateLimiterMetrics(prefix, rateLimiterRegistry.getAllRateLimiters());
75+
}
76+
77+
/**
78+
* Creates a new instance {@link RateLimiterMetrics} with
79+
* a {@link RateLimiterRegistry} as a source.
80+
*
81+
* @param rateLimiterRegistry the registry of rate limiters
82+
*/
83+
public static RateLimiterMetrics ofRateLimiterRegistry(RateLimiterRegistry rateLimiterRegistry) {
84+
return new RateLimiterMetrics(rateLimiterRegistry.getAllRateLimiters());
85+
}
86+
87+
/**
88+
* Creates a new instance {@link RateLimiterMetrics} with
89+
* an {@link Iterable} of rate limiters as a source.
90+
*
91+
* @param rateLimiters the rate limiters
92+
*/
93+
public static RateLimiterMetrics ofIterable(Iterable<RateLimiter> rateLimiters) {
94+
return new RateLimiterMetrics(rateLimiters);
95+
}
96+
97+
/**
98+
* Creates a new instance {@link RateLimiterMetrics} with
99+
* an {@link Iterable} of rate limiters as a source.
100+
*
101+
* @param rateLimiters the rate limiters
102+
*/
103+
public static RateLimiterMetrics ofIterable(String prefix, Iterable<RateLimiter> rateLimiters) {
104+
return new RateLimiterMetrics(prefix, rateLimiters);
105+
}
106+
107+
108+
/**
109+
* Creates a new instance of {@link RateLimiterMetrics} with a rate limiter as a source.
110+
*
111+
* @param rateLimiter the rate limiter
112+
*/
113+
public static RateLimiterMetrics ofRateLimiter(RateLimiter rateLimiter) {
114+
return new RateLimiterMetrics(Array.of(rateLimiter));
115+
}
116+
117+
@Override
118+
public Map<String, Metric> getMetrics() {
119+
return metricRegistry.getMetrics();
120+
}
121+
}

resilience4j-metrics/src/test/java/io/github/resilience4j/metrics/MetricsTest.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public void shouldDecorateCheckedSupplier() throws Throwable {
6161

6262
String value = timedSupplier.get();
6363

64-
// Then the counter ofCircuitBreakerRegistry metrics should be one
64+
// Then the counter ofRateLimiterRegistry metrics should be one
6565
assertThat(timer.getCount()).isEqualTo(1);
6666

6767
assertThat(value).isEqualTo("Hello world");
@@ -76,7 +76,7 @@ public void shouldDecorateRunnable() throws Throwable {
7676

7777
timedRunnable.run();
7878

79-
// Then the counter ofCircuitBreakerRegistry metrics should be one
79+
// Then the counter ofRateLimiterRegistry metrics should be one
8080
assertThat(timer.getCount()).isEqualTo(1);
8181

8282
// Then the helloWorldService should be invoked 1 time
@@ -90,7 +90,7 @@ public void shouldDecorateCheckedRunnableAndReturnWithSuccess() throws Throwable
9090

9191
timedRunnable.run();
9292

93-
// Then the counter ofCircuitBreakerRegistry metrics should be one
93+
// Then the counter ofRateLimiterRegistry metrics should be one
9494
assertThat(timer.getCount()).isEqualTo(1);
9595
// Then the helloWorldService should be invoked 1 time
9696
BDDMockito.then(helloWorldService).should(Mockito.times(1)).sayHelloWorldWithException();
@@ -108,7 +108,7 @@ public void shouldDecorateSupplierAndReturnWithException() throws Throwable {
108108
assertThat(result.isFailure()).isTrue();
109109
assertThat(result.failed().get()).isInstanceOf(RuntimeException.class);
110110

111-
// Then the counter ofCircuitBreakerRegistry metrics should be one
111+
// Then the counter ofRateLimiterRegistry metrics should be one
112112
assertThat(timer.getCount()).isEqualTo(1);
113113

114114
// Then the helloWorldService should be invoked 1 time
@@ -126,7 +126,7 @@ public void shouldDecorateSupplier() throws Throwable {
126126

127127
Stream.range(0,2).forEach((i) -> timedSupplier.get());
128128

129-
// Then the counter ofCircuitBreakerRegistry metrics should be ten
129+
// Then the counter ofRateLimiterRegistry metrics should be ten
130130
assertThat(timer.getCount()).isEqualTo(2);
131131
// Then the helloWorldService should be invoked 1 time
132132
BDDMockito.then(helloWorldService).should(times(2)).returnHelloWorld();
@@ -144,7 +144,7 @@ public void shouldDecorateFunctionAndReturnWithSuccess() throws Throwable {
144144
//Then
145145
assertThat(function.apply("Tom")).isEqualTo("Hello world Tom");
146146

147-
// Then the counter ofCircuitBreakerRegistry metrics should be ten
147+
// Then the counter ofRateLimiterRegistry metrics should be ten
148148
assertThat(timer.getCount()).isEqualTo(1);
149149
// Then the helloWorldService should be invoked 1 time
150150
BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorldWithName("Tom");
@@ -162,7 +162,7 @@ public void shouldDecorateCheckedFunctionAndReturnWithSuccess() throws Throwable
162162
//Then
163163
assertThat(function.apply("Tom")).isEqualTo("Hello world Tom");
164164

165-
// Then the counter ofCircuitBreakerRegistry metrics should be ten
165+
// Then the counter ofRateLimiterRegistry metrics should be ten
166166
assertThat(timer.getCount()).isEqualTo(1);
167167
// Then the helloWorldService should be invoked 1 time
168168
BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorldWithNameWithException("Tom");
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
*
3+
* Copyright 2017: Robert Winkler
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*
18+
*/
19+
package io.github.resilience4j.metrics;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.mockito.Mockito.mock;
23+
import static org.mockito.Mockito.times;
24+
25+
import com.codahale.metrics.MetricRegistry;
26+
import io.github.resilience4j.ratelimiter.RateLimiter;
27+
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
28+
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
29+
import io.github.resilience4j.test.HelloWorldService;
30+
import org.junit.Before;
31+
import org.junit.Test;
32+
import org.mockito.BDDMockito;
33+
34+
public class RateLimiterMetricsTest {
35+
36+
private static final int DEFAULT_LIMIT_FOR_PERIOD = RateLimiterConfig.ofDefaults().getLimitForPeriod();
37+
private MetricRegistry metricRegistry;
38+
private HelloWorldService helloWorldService;
39+
40+
@Before
41+
public void setUp() {
42+
metricRegistry = new MetricRegistry();
43+
helloWorldService = mock(HelloWorldService.class);
44+
}
45+
46+
@Test
47+
public void shouldRegisterMetrics() throws Throwable {
48+
//Given
49+
RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.ofDefaults();
50+
RateLimiter rateLimiter = rateLimiterRegistry.rateLimiter("testLimit");
51+
metricRegistry.registerAll(RateLimiterMetrics.ofRateLimiterRegistry(rateLimiterRegistry));
52+
53+
// Given the HelloWorldService returns Hello world
54+
BDDMockito.given(helloWorldService.returnHelloWorld()).willReturn("Hello world");
55+
56+
//When
57+
String value = rateLimiter.executeSupplier(helloWorldService::returnHelloWorld);
58+
59+
//Then
60+
assertThat(value).isEqualTo("Hello world");
61+
// Then the helloWorldService should be invoked 1 time
62+
BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld();
63+
assertThat(metricRegistry.getMetrics()).hasSize(2);
64+
assertThat(metricRegistry.getGauges().get("resilience4j.ratelimiter.testLimit.number_of_waiting_threads")
65+
.getValue()).isEqualTo(0);
66+
assertThat(metricRegistry.getGauges().get("resilience4j.ratelimiter.testLimit.available_permissions").getValue())
67+
.isIn(DEFAULT_LIMIT_FOR_PERIOD, DEFAULT_LIMIT_FOR_PERIOD - 1);
68+
}
69+
70+
@Test
71+
public void shouldUseCustomPrefix() throws Throwable {
72+
//Given
73+
RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.ofDefaults();
74+
RateLimiter rateLimiter = rateLimiterRegistry.rateLimiter("testLimit");
75+
metricRegistry.registerAll(RateLimiterMetrics.ofIterable("testPre", rateLimiterRegistry.getAllRateLimiters()));
76+
77+
// Given the HelloWorldService returns Hello world
78+
BDDMockito.given(helloWorldService.returnHelloWorld()).willReturn("Hello world");
79+
80+
//When
81+
String value = rateLimiter.executeSupplier(helloWorldService::returnHelloWorld);
82+
83+
//Then
84+
assertThat(value).isEqualTo("Hello world");
85+
// Then the helloWorldService should be invoked 1 time
86+
BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld();
87+
assertThat(metricRegistry.getMetrics()).hasSize(2);
88+
assertThat(metricRegistry.getGauges().get("testPre.testLimit.number_of_waiting_threads")
89+
.getValue()).isEqualTo(0);
90+
assertThat(metricRegistry.getGauges().get("testPre.testLimit.available_permissions").getValue())
91+
.isIn(DEFAULT_LIMIT_FOR_PERIOD, DEFAULT_LIMIT_FOR_PERIOD - 1);
92+
}
93+
}

0 commit comments

Comments
 (0)