Skip to content

Commit e4cdf2c

Browse files
voievodinRobWin
authored andcommitted
Issue ReactiveX#354: Add tag based micrometer metric binders
* Add micrometer circuit breaker metrics binder based on tags * Add micrometer bulkhead metrics binder based on tags * Add micrometer rate limiter metrics binder based on tags * Add micrometer retry metrics binder based on tags * Add micrometer async retry metrics binder based on tags * Add documentation for tag based metrics
1 parent 7503d9d commit e4cdf2c

File tree

13 files changed

+1339
-0
lines changed

13 files changed

+1339
-0
lines changed

resilience4j-documentation/src/docs/asciidoc/addon_guides/micrometer.adoc

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,21 @@ final Bulkhead boo = bulkheadRegistry.bulkhead("boo");
1818
// Register all bulkheads at once
1919
BulkheadMetrics bulkheadMetrics = BulkheadMetrics.ofBulkheadRegistry(bulkheadRegistry);
2020
bulkheadMetrics.bindTo(meterRegistry);
21+
22+
// Or for tag based metrics
23+
TaggedBulkheadMetrics.ofBulkheadRegistry(bulkheadRegistry).bindTo(meterRegistry);
2124
--
2225

2326
For each bulkhead this registry will export:
2427

2528
* `available_concurrent_calls` - instantaneous read of the number of currently available concurrent calls `[int]`
2629
* `max_allowed_concurrent_calls` - maximum number of allowed concurrent calls `[int]`
2730

31+
Tag based metrics:
32+
33+
* `resilience4j_bulkhead_available_concurrent_calls{name="name of bulkhead"}` - number of available concurrent calls
34+
* `resilience4j_bulkhead_max_allowed_concurrent_calls{name="name of bulkhead"}` - maximum number of allowed concurrent calls
35+
2836
===== CircuitBreaker
2937

3038
[source,java]
@@ -36,6 +44,9 @@ final CircuitBreaker boo = circuitBreakerRegistry.circuitBreaker("boo");
3644
// Register all circuit breakers at once
3745
CircuitBreakerMetrics circuitBreakerMetrics = CircuitBreakerMetrics.ofCircuitBreakerRegistry(circuitBreakerRegistry);
3846
circuitBreakerMetrics.bindTo(meterRegistry);
47+
48+
// Or for tag based metrics
49+
TaggedCircuitBreakerMetrics.ofCircuitBreakerRegistry(circuitBreakerRegistry).bindTo(meterRegistry);
3950
--
4051

4152
For each circuit breaker this registry will export:
@@ -47,6 +58,16 @@ For each circuit breaker this registry will export:
4758
* `buffered_max` - maximum number of buffered calls `[int]`
4859
* `not_permitted` - current number of not permitted calls `[int]`
4960

61+
Tag based metrics:
62+
63+
* `resilience4j_circuitbreaker_state{name="name of circuitbreaker"}` - the state of the circuit breaker where 0-CLOSED, 1-OPEN, 2-HALF-OPEN
64+
* `resilience4j_circuitbreaker_calls{name="name of circuitbreaker", kind="one of the values below"}` - the number of calls of a certain kind
65+
- `successful` - indicates successful calls
66+
- `failed` - indicates failed calls
67+
- `not_permitted` - indicates not permitted calls
68+
* `resilience4j_circuitbreaker_buffered_calls{name="name of circuitbreaker"}` - the number of buffered calls
69+
* `resilience4j_circuitbreaker_max_buffered_calls{name="name of circuitbreaker"}` - the maximum number of buffered calls
70+
5071
===== RateLimiter
5172

5273
[source,java]
@@ -57,13 +78,21 @@ final RateLimiter rateLimiter = rateLimiterRegistry.rateLimiter("testLimit");
5778
// Register rate limiters at once
5879
RateLimiterMetrics rateLimiterMetrics = RateLimiterMetrics.ofRateLimiterRegistry(rateLimiterRegistry);
5980
rateLimiterMetrics.bindTo(meterRegistry);
81+
82+
// Or for tag based metrics
83+
TaggedRateLimiterMetrics.ofRateLimiterRegistry(rateLimiterRegistry).bindTo(meterRegistry);
6084
--
6185

6286
For each rate limiter this registry will export:
6387

6488
* `available_permissions` - current number of available permissions `[int]`
6589
* `number_of_waiting_threads` - current number of threads waiting for permission `[int]`
6690

91+
Tag based metrics:
92+
93+
* `resilience4j_ratelimiter_available_permissions{name="name of ratelimiter"}` - the number of available permissions
94+
* `resilience4j_ratelimiter_waiting_threads{name="name of ratelimiter"}` - the number of threads waiting for permission
95+
6796
===== Retry
6897

6998
[source,java]
@@ -75,6 +104,9 @@ final Retry retry = retryRegistry.retry("testLimit");
75104
// Register all retries at once
76105
RetryMetrics retryMetrics = RetryMetrics.ofRetryRegistry(retryRegistry);
77106
retryMetrics.bindTo(meterRegistry);
107+
108+
// Or for tag baed metrics
109+
TaggedRetryMetrics.ofRetryRegistry(retryRegistry).bindTo(meterRegistry);
78110
--
79111

80112
For each retry this registry will export:
@@ -84,3 +116,12 @@ For each retry this registry will export:
84116
* `failed_calls_without_retry` - the number of failed calls without a retry attempt `[long]`
85117
* `failed_calls_with_retry` - the number of failed calls after all retry attempts `[long]`
86118

119+
Tag based metrics:
120+
121+
* `resilience4j_retry_calls{name="retry name", kind="one of the values below"}` - the number of calls of a certain kind
122+
- `successful_without_retry` - indicates successful calls without retry
123+
- `successful_with_retry` - indicates successful calls with retry
124+
- `failed_without_retry` - indicates failed calls without retry
125+
- `failed_with_retry` - indicates failed calls with retry
126+
127+
Note the same set of metrics is exposed for async retry, metric name is `resilience4j_async_retry_calls`.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2019 Yevhenii Voievodin
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.micrometer.tagged;
17+
18+
/** Common constants for metric binder implementations based on tags. */
19+
public final class TagNames {
20+
public static final String NAME = "name";
21+
public static final String KIND = "kind";
22+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright 2019 Yevhenii Voievodin
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.micrometer.tagged;
17+
18+
import io.github.resilience4j.micrometer.AsyncRetryMetrics;
19+
import io.github.resilience4j.retry.AsyncRetry;
20+
import io.github.resilience4j.retry.AsyncRetry.Metrics;
21+
import io.github.resilience4j.retry.AsyncRetryRegistry;
22+
import io.micrometer.core.instrument.Gauge;
23+
import io.micrometer.core.instrument.MeterRegistry;
24+
import io.micrometer.core.instrument.binder.MeterBinder;
25+
26+
import static java.util.Objects.requireNonNull;
27+
28+
/**
29+
* A micrometer binder that is used to register retry exposed {@link Metrics metrics}.
30+
* The main difference from {@link AsyncRetryMetrics} is that this binder uses tags
31+
* to distinguish between metrics.
32+
*/
33+
public class TaggedAsyncRetryMetrics implements MeterBinder {
34+
35+
/**
36+
* Creates a new binder that uses given {@code registry} as source of retries.
37+
*
38+
* @param registry the source of async retries
39+
*/
40+
public static TaggedAsyncRetryMetrics ofAsyncRetryRegistry(AsyncRetryRegistry registry) {
41+
return new TaggedAsyncRetryMetrics(MetricNames.ofDefaults(), registry.getAllRetries());
42+
}
43+
44+
/**
45+
* Creates a new binder that uses given {@code registry} as source of retries.
46+
*
47+
* @param names custom metric names
48+
* @param registry the source of retries
49+
*/
50+
public static TaggedAsyncRetryMetrics ofAsyncRetryRegistry(MetricNames names, AsyncRetryRegistry registry) {
51+
return new TaggedAsyncRetryMetrics(names, registry.getAllRetries());
52+
}
53+
54+
private final MetricNames names;
55+
private final Iterable<? extends AsyncRetry> asyncRetries;
56+
57+
private TaggedAsyncRetryMetrics(MetricNames names, Iterable<? extends AsyncRetry> asyncRetries) {
58+
this.names = requireNonNull(names);
59+
this.asyncRetries = requireNonNull(asyncRetries);
60+
}
61+
62+
@Override
63+
public void bindTo(MeterRegistry registry) {
64+
for (AsyncRetry asyncRetry : asyncRetries) {
65+
Gauge.builder(names.getCallsMetricName(), asyncRetry, (art) -> art.getMetrics().getNumberOfSuccessfulCallsWithoutRetryAttempt())
66+
.tag(TagNames.NAME, asyncRetry.getName())
67+
.tag(TagNames.KIND, "successful_without_retry")
68+
.register(registry);
69+
Gauge.builder(names.getCallsMetricName(), asyncRetry, (art) -> art.getMetrics().getNumberOfSuccessfulCallsWithRetryAttempt())
70+
.tag(TagNames.NAME, asyncRetry.getName())
71+
.tag(TagNames.KIND, "successful_with_retry")
72+
.register(registry);
73+
Gauge.builder(names.getCallsMetricName(), asyncRetry, (art) -> art.getMetrics().getNumberOfFailedCallsWithoutRetryAttempt())
74+
.tag(TagNames.NAME, asyncRetry.getName())
75+
.tag(TagNames.KIND, "failed_without_retry")
76+
.register(registry);
77+
Gauge.builder(names.getCallsMetricName(), asyncRetry, (art) -> art.getMetrics().getNumberOfFailedCallsWithRetryAttempt())
78+
.tag(TagNames.NAME, asyncRetry.getName())
79+
.tag(TagNames.KIND, "failed_with_retry")
80+
.register(registry);
81+
}
82+
}
83+
84+
/** Defines possible configuration for metric names. */
85+
public static class MetricNames {
86+
87+
public static final String DEFAULT_ASYNC_RETRY_CALLS = "resilience4j_async_retry_calls";
88+
89+
/**
90+
* Returns a builder for creating custom metric names.
91+
* Note that names have default values, so only desired metrics can be renamed.
92+
*/
93+
public static Builder custom() {
94+
return new Builder();
95+
}
96+
97+
/** Returns default metric names. */
98+
public static MetricNames ofDefaults() {
99+
return new MetricNames();
100+
}
101+
102+
private String callsMetricName = DEFAULT_ASYNC_RETRY_CALLS;
103+
104+
private MetricNames() {}
105+
106+
/** Returns the metric name for async retry calls, defaults to {@value DEFAULT_ASYNC_RETRY_CALLS}. */
107+
public String getCallsMetricName() {
108+
return callsMetricName;
109+
}
110+
111+
/** Helps building custom instance of {@link MetricNames}. */
112+
public static class Builder {
113+
private final MetricNames metricNames = new MetricNames();
114+
115+
/** Overrides the default metric name {@value MetricNames#DEFAULT_ASYNC_RETRY_CALLS} with a given one. */
116+
public Builder callsMetricName(String callsMetricName) {
117+
metricNames.callsMetricName = requireNonNull(callsMetricName);
118+
return this;
119+
}
120+
121+
/** Builds {@link MetricNames} instance. */
122+
public MetricNames build() {
123+
return metricNames;
124+
}
125+
}
126+
}
127+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2019 Yevhenii Voievodin
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.micrometer.tagged;
17+
18+
import io.github.resilience4j.bulkhead.Bulkhead;
19+
import io.github.resilience4j.bulkhead.Bulkhead.Metrics;
20+
import io.github.resilience4j.bulkhead.BulkheadRegistry;
21+
import io.github.resilience4j.micrometer.BulkheadMetrics;
22+
import io.micrometer.core.instrument.Gauge;
23+
import io.micrometer.core.instrument.MeterRegistry;
24+
import io.micrometer.core.instrument.binder.MeterBinder;
25+
26+
import static java.util.Objects.requireNonNull;
27+
28+
/**
29+
* A micrometer binder that is used to register bulkhead exposed {@link Metrics metrics}.
30+
* The main difference from {@link BulkheadMetrics} is that this binder uses tags
31+
* to distinguish between metrics.
32+
*/
33+
public class TaggedBulkheadMetrics implements MeterBinder {
34+
35+
/**
36+
* Creates a new binder that uses given {@code registry} as source of bulkheads.
37+
*
38+
* @param registry the source of bulkheads
39+
*/
40+
public static TaggedBulkheadMetrics ofBulkheadRegistry(BulkheadRegistry registry) {
41+
return new TaggedBulkheadMetrics(MetricNames.ofDefaults(), registry.getAllBulkheads());
42+
}
43+
44+
/**
45+
* Creates a new binder defining custom metric names and
46+
* using given {@code registry} as source of bulkheads.
47+
*
48+
* @param names custom names of the metrics
49+
* @param registry the source of bulkheads
50+
*/
51+
public static TaggedBulkheadMetrics ofBulkheadRegistry(MetricNames names, BulkheadRegistry registry) {
52+
return new TaggedBulkheadMetrics(names, registry.getAllBulkheads());
53+
}
54+
55+
private final MetricNames names;
56+
private final Iterable<? extends Bulkhead> bulkheads;
57+
58+
private TaggedBulkheadMetrics(MetricNames names, Iterable<? extends Bulkhead> bulkheads) {
59+
this.names = requireNonNull(names);
60+
this.bulkheads = requireNonNull(bulkheads);
61+
}
62+
63+
@Override
64+
public void bindTo(MeterRegistry registry) {
65+
for (Bulkhead bulkhead : bulkheads) {
66+
Gauge.builder(names.getAvailableConcurrentCallsMetricName(), bulkhead, (bh) -> bh.getMetrics().getAvailableConcurrentCalls())
67+
.tag(TagNames.NAME, bulkhead.getName())
68+
.register(registry);
69+
Gauge.builder(names.getMaxAllowedConcurrentCallsMetricName(), bulkhead, (bh) -> bh.getMetrics().getMaxAllowedConcurrentCalls())
70+
.tag(TagNames.NAME, bulkhead.getName())
71+
.register(registry);
72+
}
73+
}
74+
75+
/** Defines possible configuration for metric names. */
76+
public static class MetricNames {
77+
78+
public static final String DEFAULT_BULKHEAD_AVAILABLE_CONCURRENT_CALLS_METRIC_NAME = "resilience4j_bulkhead_available_concurrent_calls";
79+
public static final String DEFAULT_BULKHEAD_MAX_ALLOWED_CONCURRENT_CALLS_METRIC_NAME = "resilience4j_bulkhead_max_allowed_concurrent_calls";
80+
81+
/**
82+
* Returns a builder for creating custom metric names.
83+
* Note that names have default values, so only desired metrics can be renamed.
84+
*/
85+
public static Builder custom() {
86+
return new Builder();
87+
}
88+
89+
/** Returns default metric names. */
90+
public static MetricNames ofDefaults() {
91+
return new MetricNames();
92+
}
93+
94+
private String availableConcurrentCallsMetricName = DEFAULT_BULKHEAD_AVAILABLE_CONCURRENT_CALLS_METRIC_NAME;
95+
private String maxAllowedConcurrentCallsMetricName = DEFAULT_BULKHEAD_MAX_ALLOWED_CONCURRENT_CALLS_METRIC_NAME;
96+
97+
private MetricNames() {}
98+
99+
/**
100+
* Returns the metric name for bulkhead concurrent calls,
101+
* defaults to {@value DEFAULT_BULKHEAD_AVAILABLE_CONCURRENT_CALLS_METRIC_NAME}.
102+
*/
103+
public String getAvailableConcurrentCallsMetricName() {
104+
return availableConcurrentCallsMetricName;
105+
}
106+
107+
/**
108+
* Returns the metric name for bulkhead max available concurrent calls,
109+
* defaults to {@value DEFAULT_BULKHEAD_MAX_ALLOWED_CONCURRENT_CALLS_METRIC_NAME}.
110+
*/
111+
public String getMaxAllowedConcurrentCallsMetricName() {
112+
return maxAllowedConcurrentCallsMetricName;
113+
}
114+
115+
/** Helps building custom instance of {@link MetricNames}. */
116+
public static class Builder {
117+
118+
private final MetricNames metricNames = new MetricNames();
119+
120+
/** Overrides the default metric name {@value MetricNames#DEFAULT_BULKHEAD_AVAILABLE_CONCURRENT_CALLS_METRIC_NAME} with a given one. */
121+
public Builder availableConcurrentCallsMetricName(String availableConcurrentCallsMetricNames) {
122+
metricNames.availableConcurrentCallsMetricName = requireNonNull(availableConcurrentCallsMetricNames);
123+
return this;
124+
}
125+
126+
/** Overrides the default metric name {@value MetricNames#DEFAULT_BULKHEAD_MAX_ALLOWED_CONCURRENT_CALLS_METRIC_NAME} with a given one. */
127+
public Builder maxAllowedConcurrentCallsMetricName(String maxAllowedConcurrentCallsMetricName) {
128+
metricNames.maxAllowedConcurrentCallsMetricName = requireNonNull(maxAllowedConcurrentCallsMetricName);
129+
return this;
130+
}
131+
132+
/** Builds {@link MetricNames} instance. */
133+
public MetricNames build() {
134+
return metricNames;
135+
}
136+
}
137+
}
138+
}

0 commit comments

Comments
 (0)