Skip to content

Commit 4308019

Browse files
madgnomeRobWin
authored andcommitted
Add support for Micrometer ReactiveX#162 (ReactiveX#206)
1 parent 97b695b commit 4308019

File tree

25 files changed

+587
-36
lines changed

25 files changed

+587
-36
lines changed

libraries.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ ext {
2121
prometheusSimpleClientVersion = '0.0.21'
2222
reactorVersion = '3.1.3.RELEASE'
2323
reactiveStreamsVersion = '1.0.2'
24+
micrometerVersion = '1.0.0'
2425

2526
libraries = [
2627
// compile
@@ -67,6 +68,9 @@ ext {
6768
// Metrics addon
6869
metrics: "io.dropwizard.metrics:metrics-core:${metricsVersion}",
6970

71+
// Micrometers addon
72+
micrometer: "io.micrometer:micrometer-core:${micrometerVersion}",
73+
7074
// CircuitBreaker documentation
7175
metrics_healthcheck: "io.dropwizard.metrics:metrics-healthchecks:${metricsVersion}",
7276

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.github.resilience4j.bulkhead.utils;
2+
3+
public class MetricNames {
4+
public static final String DEFAULT_PREFIX = "resilience4j.bulkhead";
5+
public static final String AVAILABLE_CONCURRENT_CALLS = "available_concurrent_calls";
6+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package io.github.resilience4j.circuitbreaker.utils;
2+
3+
public class MetricNames {
4+
public static final String DEFAULT_PREFIX = "resilience4j.circuitbreaker";
5+
public static final String SUCCESSFUL = "successful";
6+
public static final String FAILED = "failed";
7+
public static final String NOT_PERMITTED = "not_permitted";
8+
public static final String BUFFERED = "buffered";
9+
public static final String BUFFERED_MAX = "buffered_max";
10+
public static final String STATE = "state";
11+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
=== Micrometer Metrics exporter
2+
3+
==== Introduction
4+
5+
Integration with http://micrometer.io/[Micrometer].
6+
With this add-on you can easily add your bulkhead, circuit breaker, rate limiter, retry metrics in Micrometer `MeterRegistry`.
7+
8+
==== Usage
9+
10+
===== Bulkhead
11+
12+
[source,java]
13+
--
14+
final BulkheadRegistry bulkheadRegistry = BulkheadRegistry.ofDefaults();
15+
final Bulkhead foo = bulkheadRegistry.bulkhead("foo");
16+
final Bulkhead boo = bulkheadRegistry.bulkhead("boo");
17+
18+
// Register all bulkheads at once
19+
BulkheadMetrics bulkheadMetrics = BulkheadMetrics.ofBulkheadRegistry(bulkheadRegistry);
20+
bulkheadMetrics.bindTo(meterRegistry);
21+
--
22+
23+
For each bulkhead this registry will export:
24+
25+
* `available_concurrent_calls` - instantaneous read of the number of currently available concurrent calls `[int]`
26+
27+
===== CircuitBreaker
28+
29+
[source,java]
30+
--
31+
final CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
32+
final CircuitBreaker foo = circuitBreakerRegistry.circuitBreaker("foo");
33+
final CircuitBreaker boo = circuitBreakerRegistry.circuitBreaker("boo");
34+
35+
// Register all circuit breakers at once
36+
CircuitBreakerMetrics circuitBreakerMetrics = CircuitBreakerMetrics.ofCircuitBreakerRegistry(circuitBreakerRegistry);
37+
circuitBreakerMetrics.bindTo(meterRegistry);
38+
--
39+
40+
For each circuit breaker this registry will export:
41+
42+
* `state` - instantaneous read of the current state where 0-CLOSED, 1-OPEN, 2-HALF-OPEN `[int]`
43+
* `successful` - current number of successful calls `[int]`
44+
* `failed` - current number of failed calls `[int]`
45+
* `buffered` - current number of buffered calls `[int]`
46+
* `buffered_max` - maximum number of buffered calls `[int]`
47+
* `not_permitted` - current number of not permitted calls `[int]`
48+
49+
===== RateLimiter
50+
51+
[source,java]
52+
--
53+
final RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.ofDefaults();
54+
final RateLimiter rateLimiter = rateLimiterRegistry.rateLimiter("testLimit");
55+
56+
// Register rate limiters at once
57+
RateLimiterMetrics rateLimiterMetrics = RateLimiterMetrics.ofRateLimiterRegistry(rateLimiterRegistry);
58+
rateLimiterMetrics.bindTo(meterRegistry);
59+
--
60+
61+
For each rate limiter this registry will export:
62+
63+
* `available_permissions` - current number of available permissions `[int]`
64+
* `number_of_waiting_threads` - current number of threads waiting for permission `[int]`
65+
66+
===== Retry
67+
68+
[source,java]
69+
--
70+
final MetricRegistry metricRegistry = new MetricRegistry();
71+
final RetryRegistry retryRegistry = RetryRegistry.ofDefaults();
72+
final Retry retry = retryRegistry.retry("testLimit");
73+
74+
// Register all retries at once
75+
RetryMetrics retryMetrics = RetryMetrics.ofRetryRegistry(retryRegistry);
76+
retryMetrics.bindTo(meterRegistry);
77+
--
78+
79+
For each retry this registry will export:
80+
81+
* `successful_calls_without_retry` - the number of successful calls without a retry attempt `[long]`
82+
* `successful_calls_with_retry` - the number of successful calls after a retry attempt `[long]`
83+
* `failed_calls_without_retry` - the number of failed calls without a retry attempt `[long]`
84+
* `failed_calls_with_retry` - the number of failed calls after all retry attempts `[long]`
85+

resilience4j-documentation/src/docs/asciidoc/addons_guide.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,6 @@ include::addon_guides/retrofit.adoc[]
88

99
include::addon_guides/dropwizard.adoc[]
1010

11-
include::addon_guides/prometheus.adoc[]
11+
include::addon_guides/prometheus.adoc[]
12+
13+
include::addon_guides/micrometer.adoc[]

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@
2626
import java.util.Map;
2727

2828
import static com.codahale.metrics.MetricRegistry.name;
29+
import static io.github.resilience4j.bulkhead.utils.MetricNames.AVAILABLE_CONCURRENT_CALLS;
30+
import static io.github.resilience4j.bulkhead.utils.MetricNames.DEFAULT_PREFIX;
2931
import static java.util.Objects.requireNonNull;
3032

3133
/**
3234
* An adapter which exports {@link Bulkhead.Metrics} as Dropwizard Metrics Gauges.
3335
*/
3436
public class BulkheadMetrics implements MetricSet {
35-
private static final String DEFAULT_PREFIX = "resilience4j.bulkhead";
36-
private static final String AVAILABLE_CONCURRENT_CALLS = "available_concurrent_calls";
37-
3837
private final MetricRegistry metricRegistry = new MetricRegistry();
3938

4039
private BulkheadMetrics(Iterable<Bulkhead> bulkheads) {

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,14 @@
2929
import java.util.Map;
3030

3131
import static com.codahale.metrics.MetricRegistry.name;
32+
import static io.github.resilience4j.circuitbreaker.utils.MetricNames.*;
3233
import static java.util.Objects.requireNonNull;
3334

3435
/**
3536
* An adapter which exports {@link CircuitBreaker.Metrics} as Dropwizard Metrics Gauges.
3637
*/
3738
public class CircuitBreakerMetrics implements MetricSet {
3839

39-
private static final String DEFAULT_PREFIX = "resilience4j.circuitbreaker";
40-
public static final String SUCCESSFUL = "successful";
41-
public static final String FAILED = "failed";
42-
public static final String NOT_PERMITTED = "not_permitted";
43-
public static final String BUFFERED = "buffered";
44-
public static final String BUFFERED_MAX = "buffered_max";
45-
public static final String STATE = "state";
46-
4740
private final MetricRegistry metricRegistry = new MetricRegistry();
4841

4942
private CircuitBreakerMetrics(Iterable<CircuitBreaker> circuitBreakers) {

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
import java.util.Map;
3030

3131
import static com.codahale.metrics.MetricRegistry.name;
32+
import static io.github.resilience4j.ratelimiter.utils.MetricNames.AVAILABLE_PERMISSIONS;
33+
import static io.github.resilience4j.ratelimiter.utils.MetricNames.DEFAULT_PREFIX;
34+
import static io.github.resilience4j.ratelimiter.utils.MetricNames.WAITING_THREADS;
3235
import static java.util.Objects.requireNonNull;
3336

3437
/**
@@ -39,8 +42,6 @@ public class RateLimiterMetrics implements MetricSet {
3942
private static final String PREFIX_NULL = "Prefix must not be null";
4043
private static final String ITERABLE_NULL = "RateLimiters iterable must not be null";
4144

42-
private static final String DEFAULT_PREFIX = "resilience4j.ratelimiter";
43-
4445
private final MetricRegistry metricRegistry = new MetricRegistry();
4546

4647
private RateLimiterMetrics(Iterable<RateLimiter> rateLimiters) {
@@ -53,9 +54,9 @@ private RateLimiterMetrics(String prefix, Iterable<RateLimiter> rateLimiters) {
5354

5455
rateLimiters.forEach(rateLimiter -> {
5556
String name = rateLimiter.getName();
56-
metricRegistry.register(name(prefix, name, "number_of_waiting_threads"),
57+
metricRegistry.register(name(prefix, name, WAITING_THREADS),
5758
(Gauge<Integer>) rateLimiter.getMetrics()::getNumberOfWaitingThreads);
58-
metricRegistry.register(name(prefix, name, "available_permissions"),
59+
metricRegistry.register(name(prefix, name, AVAILABLE_PERMISSIONS),
5960
(Gauge<Integer>) rateLimiter.getMetrics()::getAvailablePermissions);
6061
}
6162
);

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,15 @@
88
import java.util.Map;
99

1010
import static com.codahale.metrics.MetricRegistry.name;
11+
import static io.github.resilience4j.retry.utils.MetricNames.*;
1112
import static java.util.Objects.requireNonNull;
1213

1314
/**
1415
* An adapter which exports {@link Retry.Metrics} as Dropwizard Metrics Gauges.
1516
*/
1617
public class RetryMetrics implements MetricSet {
1718

18-
public static final String SUCCESSFUL_CALLS_WITHOUT_RETRY = "successful_calls_without_retry";
19-
public static final String SUCCESSFUL_CALLS_WITH_RETRY = "successful_calls_with_retry";
20-
public static final String FAILED_CALLS_WITHOUT_RETRY = "failed_calls_without_retry";
21-
public static final String FAILED_CALLS_WITH_RETRY = "failed_calls_with_retry";
2219
private final MetricRegistry metricRegistry = new MetricRegistry();
23-
private static final String DEFAULT_PREFIX = "resilience4j.retry";
2420

2521
private RetryMetrics(Iterable<Retry> retries){
2622
this(DEFAULT_PREFIX, retries);

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

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import javax.xml.ws.WebServiceException;
1313

14+
import static io.github.resilience4j.retry.utils.MetricNames.*;
1415
import static org.assertj.core.api.Assertions.assertThat;
1516
import static org.mockito.Mockito.mock;
1617
import static org.mockito.Mockito.times;
@@ -44,10 +45,10 @@ public void shouldRegisterMetricsWithoutRetry() throws Throwable {
4445
// Then the helloWorldService should be invoked 1 time
4546
BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld();
4647
assertThat(metricRegistry.getMetrics()).hasSize(4);
47-
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + RetryMetrics.SUCCESSFUL_CALLS_WITH_RETRY).getValue()).isEqualTo(0L);
48-
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + RetryMetrics.SUCCESSFUL_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(1L);
49-
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + RetryMetrics.FAILED_CALLS_WITH_RETRY).getValue()).isEqualTo(0L);
50-
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + RetryMetrics.FAILED_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L);
48+
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + SUCCESSFUL_CALLS_WITH_RETRY).getValue()).isEqualTo(0L);
49+
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + SUCCESSFUL_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(1L);
50+
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + FAILED_CALLS_WITH_RETRY).getValue()).isEqualTo(0L);
51+
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + FAILED_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L);
5152
}
5253

5354
@Test
@@ -74,10 +75,10 @@ public void shouldRegisterMetricsWithRetry() throws Throwable {
7475
// Then the helloWorldService should be invoked 1 time
7576
BDDMockito.then(helloWorldService).should(times(5)).returnHelloWorld();
7677
assertThat(metricRegistry.getMetrics()).hasSize(4);
77-
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + RetryMetrics.SUCCESSFUL_CALLS_WITH_RETRY).getValue()).isEqualTo(1L);
78-
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + RetryMetrics.SUCCESSFUL_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L);
79-
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + RetryMetrics.FAILED_CALLS_WITH_RETRY).getValue()).isEqualTo(1L);
80-
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + RetryMetrics.FAILED_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L);
78+
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + SUCCESSFUL_CALLS_WITH_RETRY).getValue()).isEqualTo(1L);
79+
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + SUCCESSFUL_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L);
80+
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + FAILED_CALLS_WITH_RETRY).getValue()).isEqualTo(1L);
81+
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName." + FAILED_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L);
8182
}
8283

8384
@Test
@@ -97,9 +98,9 @@ public void shouldUseCustomPrefix() throws Throwable {
9798
// Then the helloWorldService should be invoked 1 time
9899
BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld();
99100
assertThat(metricRegistry.getMetrics()).hasSize(4);
100-
assertThat(metricRegistry.getGauges().get("testPrefix.testName." + RetryMetrics.SUCCESSFUL_CALLS_WITH_RETRY).getValue()).isEqualTo(0L);
101-
assertThat(metricRegistry.getGauges().get("testPrefix.testName." + RetryMetrics.SUCCESSFUL_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(1L);
102-
assertThat(metricRegistry.getGauges().get("testPrefix.testName." + RetryMetrics.FAILED_CALLS_WITH_RETRY).getValue()).isEqualTo(0L);
103-
assertThat(metricRegistry.getGauges().get("testPrefix.testName." + RetryMetrics.FAILED_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L);
101+
assertThat(metricRegistry.getGauges().get("testPrefix.testName." + SUCCESSFUL_CALLS_WITH_RETRY).getValue()).isEqualTo(0L);
102+
assertThat(metricRegistry.getGauges().get("testPrefix.testName." + SUCCESSFUL_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(1L);
103+
assertThat(metricRegistry.getGauges().get("testPrefix.testName." + FAILED_CALLS_WITH_RETRY).getValue()).isEqualTo(0L);
104+
assertThat(metricRegistry.getGauges().get("testPrefix.testName." + FAILED_CALLS_WITHOUT_RETRY).getValue()).isEqualTo(0L);
104105
}
105106
}

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import java.util.function.Function;
4040
import java.util.function.Supplier;
4141

42+
import static com.jayway.awaitility.Awaitility.await;
43+
import static java.util.concurrent.TimeUnit.SECONDS;
4244
import static org.assertj.core.api.Assertions.assertThat;
4345
import static org.mockito.Mockito.mock;
4446
import static org.mockito.Mockito.times;
@@ -158,9 +160,12 @@ public void shouldExecuteCompletionStageSupplier() throws Throwable {
158160

159161
assertThat(value).isEqualTo("Hello world");
160162

161-
assertThat(timer.getMetrics().getNumberOfTotalCalls()).isEqualTo(1);
162-
assertThat(timer.getMetrics().getNumberOfSuccessfulCalls()).isEqualTo(1);
163-
assertThat(timer.getMetrics().getNumberOfFailedCalls()).isEqualTo(0);
163+
await().atMost(1, SECONDS)
164+
.until(() -> {
165+
assertThat(timer.getMetrics().getNumberOfTotalCalls()).isEqualTo(1);
166+
assertThat(timer.getMetrics().getNumberOfSuccessfulCalls()).isEqualTo(1);
167+
assertThat(timer.getMetrics().getNumberOfFailedCalls()).isEqualTo(0);
168+
});
164169

165170
// Then the helloWorldService should be invoked 1 time
166171
BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld();

resilience4j-micrometer/README.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
= resilience4j-micrometer
2+
3+
Integration of bulkhead, circuit breaker, rate limiter and retry metrics with http://micrometer.io/[Micrometer].
4+
5+
NOTE: For detailed documentation look at our *http://resilience4j.github.io/resilience4j/#_micrometer_metrics_exporter[Usage Guide]*
6+
7+
== License
8+
9+
Copyright 2018 Julien Hoarau
10+
11+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
12+
13+
http://www.apache.org/licenses/LICENSE-2.0
14+
15+
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

resilience4j-micrometer/build.gradle

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
dependencies {
2+
compile (libraries.micrometer)
3+
compileOnly project(':resilience4j-bulkhead')
4+
compileOnly project(':resilience4j-circuitbreaker')
5+
compileOnly project(':resilience4j-retry')
6+
compileOnly project(':resilience4j-ratelimiter')
7+
testCompile project(':resilience4j-test')
8+
testCompile project(':resilience4j-bulkhead')
9+
testCompile project(':resilience4j-circuitbreaker')
10+
testCompile project(':resilience4j-ratelimiter')
11+
testCompile project(':resilience4j-retry')
12+
testCompile project(':resilience4j-test')
13+
testCompile project(':resilience4j-circuitbreaker')
14+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package io.github.resilience4j.micrometer;
2+
3+
import io.github.resilience4j.bulkhead.Bulkhead;
4+
import io.github.resilience4j.bulkhead.BulkheadRegistry;
5+
import io.micrometer.core.instrument.Gauge;
6+
import io.micrometer.core.instrument.MeterRegistry;
7+
import io.micrometer.core.instrument.binder.MeterBinder;
8+
9+
import static io.github.resilience4j.bulkhead.utils.MetricNames.AVAILABLE_CONCURRENT_CALLS;
10+
import static io.github.resilience4j.bulkhead.utils.MetricNames.DEFAULT_PREFIX;
11+
import static io.github.resilience4j.micrometer.MetricUtils.getName;
12+
import static java.util.Objects.requireNonNull;
13+
14+
public class BulkheadMetrics implements MeterBinder {
15+
16+
private final Iterable<Bulkhead> bulkheads;
17+
private final String prefix;
18+
19+
private BulkheadMetrics(Iterable<Bulkhead> bulkheads) {
20+
this(bulkheads, DEFAULT_PREFIX);
21+
}
22+
23+
private BulkheadMetrics(Iterable<Bulkhead> bulkheads, String prefix) {
24+
this.bulkheads = requireNonNull(bulkheads);
25+
this.prefix = requireNonNull(prefix);
26+
}
27+
28+
/**
29+
* Creates a new instance BulkheadMetrics {@link BulkheadMetrics} with
30+
* a {@link BulkheadRegistry} as a source.
31+
*
32+
* @param bulkheadRegistry the registry of bulkheads
33+
*/
34+
public static BulkheadMetrics ofBulkheadRegistry(BulkheadRegistry bulkheadRegistry) {
35+
return new BulkheadMetrics(bulkheadRegistry.getAllBulkheads());
36+
}
37+
38+
@Override
39+
public void bindTo(MeterRegistry registry) {
40+
for (Bulkhead bulkhead : bulkheads) {
41+
final String name = bulkhead.getName();
42+
Gauge.builder(getName(prefix, name, AVAILABLE_CONCURRENT_CALLS), bulkhead, (cb) -> cb.getMetrics().getAvailableConcurrentCalls())
43+
.register(registry);
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)