diff --git a/libraries.gradle b/libraries.gradle index dc8403fe03..b944b4af75 100644 --- a/libraries.gradle +++ b/libraries.gradle @@ -15,6 +15,7 @@ ext { vertxVersion = '3.4.1' springBootVersion = '1.4.3.RELEASE' retrofitVersion = '2.1.0' + prometheusSimpleClientVersion = '0.0.21' libraries = [ // compile @@ -52,6 +53,9 @@ ext { metrics: "io.dropwizard.metrics:metrics-core:${metricsVersion}", // CircuitBreaker documentation - metrics_healthcheck: "io.dropwizard.metrics:metrics-healthchecks:${metricsVersion}" + metrics_healthcheck: "io.dropwizard.metrics:metrics-healthchecks:${metricsVersion}", + + // Prometheus addon + prometheus_simpleclient: "io.prometheus:simpleclient_common:${prometheusSimpleClientVersion}" ] } diff --git a/resilience4j-prometheus/README.adoc b/resilience4j-prometheus/README.adoc new file mode 100644 index 0000000000..95c04ab8ed --- /dev/null +++ b/resilience4j-prometheus/README.adoc @@ -0,0 +1,70 @@ += resilience4j-prometheus + +Direct integration of circuit breaker and rate limiter metrics with +https://github.com/prometheus/client_java[Prometheus simple client] + +For the circuit breaker library exports 2 metrics: + +1. By state with default metric name `circuit_breaker_states` and label `state`: + + - `closed` + - `open` + - `half_open` + +2. By call result with default metric name `circuit_breaker_calls` and label `call_result`: + + - `successful` + - `failed` + - `not_permitted` + - `buffered` + - `buffered_max` + +For the rate limiter following metric with default name `rate_limiter` and label `param` exported: + +- `available_permissions` +- `waiting_threads` + +The names of the rate limiters and circuit breakers are exposed using label `name`. + +== Usage + +=== CircuitBreaker + +[source,java] +-- +final CircuitBreakerRegistry circuitBreakerRegistry = new InMemoryCircuitBreakerRegistry(); + +final CircuitBreaker foo = circuitBreakerRegistry.circuitBreaker("foo"); +final CircuitBreaker boo = circuitBreakerRegistry.circuitBreaker("boo"); + +// Registering metrics in default prometeus CollectorRegistry +new CircuitBreakerExports(circuitBreakerRegistry).register(); +-- + +=== RateLimiter + +[source,java] +-- +final RateLimiterRegistry rateLimiterRegistry = new InMemoryRateLimiterRegistry(); + +final RateLimiter foo = rateLimiterRegistry.rateLimiter("foo"); +final RateLimiter boo = rateLimiterRegistry.rateLimiter("boo"); + +// Registering metrics in default prometeus CollectorRegistry +new RateLimiterExports(rateLimiterRegistry).register(); +-- + +For both it is possible to use just a collection of breakers and limiters instead of registry. + +== License + +Copyright 2017 Oleksandr Goldobin + +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 + + http://www.apache.org/licenses/LICENSE-2.0 + +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. \ No newline at end of file diff --git a/resilience4j-prometheus/build.gradle b/resilience4j-prometheus/build.gradle new file mode 100644 index 0000000000..33c0abb959 --- /dev/null +++ b/resilience4j-prometheus/build.gradle @@ -0,0 +1,5 @@ +dependencies { + compile (libraries.prometheus_simpleclient) + compile project(':resilience4j-circuitbreaker') + compile project(':resilience4j-ratelimiter') +} diff --git a/resilience4j-prometheus/src/main/java/io/github/resilience4j/prometheus/CircuitBreakerExports.java b/resilience4j-prometheus/src/main/java/io/github/resilience4j/prometheus/CircuitBreakerExports.java new file mode 100644 index 0000000000..cefcd05b38 --- /dev/null +++ b/resilience4j-prometheus/src/main/java/io/github/resilience4j/prometheus/CircuitBreakerExports.java @@ -0,0 +1,170 @@ +/* + * + * Copyright 2017 Oleksandr Goldobin + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + * + * + */ +package io.github.resilience4j.prometheus; + +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.prometheus.client.Collector; +import io.prometheus.client.GaugeMetricFamily; +import javaslang.Tuple; +import javaslang.Tuple2; +import javaslang.collection.Array; + +import java.util.List; +import java.util.function.Supplier; + +import static java.util.Arrays.asList; +import static java.util.Objects.requireNonNull; + +/** + * An adapter from builtin {@link CircuitBreaker.Metrics} to prometheus + * {@link io.prometheus.client.CollectorRegistry}. + * + * Also exports {@link CircuitBreaker} state as a labeled metric + */ +public class CircuitBreakerExports extends Collector { + + private static final String DEFAULT_NAME = "circuit_breaker"; + private static Array> STATE_NAME_MAP = + Array.ofAll(asList(CircuitBreaker.State.values())) + .map(state -> Tuple.of(state, state.name().toLowerCase())); + + private final String prefix; + private final Supplier> circuitBreakersSupplier; + + /** + * Creates a new instance of {@link CircuitBreakerExports} with default metrics names prefix and + * {@link CircuitBreakerRegistry} as a source of circuit breakers. + + * @param circuitBreakerRegistry the registry of circuit breakers + */ + public CircuitBreakerExports(CircuitBreakerRegistry circuitBreakerRegistry) { + this(circuitBreakerRegistry::getAllCircuitBreakers); + } + + /** + * Creates a new instance of {@link CircuitBreakerExports} with default metrics names prefix and + * {@link Iterable} of circuit breakers. + * + * @param circuitBreakers the circuit breakers + */ + public CircuitBreakerExports(Iterable circuitBreakers) { + this(() -> circuitBreakers); + } + + + /** + * Creates a new instance of {@link CircuitBreakerExports} with default metrics names prefix and + * {@link Supplier} of circuit breakers + * + * @param circuitBreakersSupplier the supplier of circuit breakers + */ + public CircuitBreakerExports(Supplier> circuitBreakersSupplier) { + this(DEFAULT_NAME, circuitBreakersSupplier); + } + + /** + * Creates a new instance of {@link CircuitBreakerExports} with specified metrics names prefix and + * {@link CircuitBreakerRegistry} as a source of circuit breakers. + * + * @param prefix the prefix of metrics names + * @param circuitBreakerRegistry the registry of circuit breakers + */ + public CircuitBreakerExports(String prefix, CircuitBreakerRegistry circuitBreakerRegistry) { + this(prefix, circuitBreakerRegistry::getAllCircuitBreakers); + } + + /** + * Creates a new instance of {@link CircuitBreakerExports} with specified metrics names prefix and + * {@link Iterable} of circuit breakers. + * + * @param prefix the prefix of metrics names + * @param circuitBreakers the circuit breakers + */ + public CircuitBreakerExports(String prefix, Iterable circuitBreakers) { + this(prefix, () -> circuitBreakers); + } + + /** + * Creates a new instance of {@link CircuitBreakerExports} with specified metrics names prefix and + * {@link Supplier} of circuit breakers + * + * @param prefix the prefix of metrics names + * @param circuitBreakersSupplier the supplier of circuit breakers + */ + public CircuitBreakerExports(String prefix, Supplier> circuitBreakersSupplier) { + requireNonNull(prefix); + requireNonNull(circuitBreakersSupplier); + + this.prefix = prefix; + this.circuitBreakersSupplier = circuitBreakersSupplier; + } + + /** + * {@inheritDoc} + */ + @Override + public List collect() { + + final GaugeMetricFamily states = new GaugeMetricFamily( + prefix + "_states", + "Circuit Breaker States", + asList("name","state")); + + final GaugeMetricFamily calls = new GaugeMetricFamily( + prefix + "_calls", + "Circuit Breaker Call Stats", + asList("name", "call_result")); + + for (CircuitBreaker circuitBreaker : circuitBreakersSupplier.get()) { + + STATE_NAME_MAP.forEach(e -> { + final CircuitBreaker.State state = e._1; + final String name = e._2; + final double value = state == circuitBreaker.getState() ? 1.0 : 0.0; + + states.addMetric(asList(circuitBreaker.getName(), name), value); + }); + + final CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics(); + + calls.addMetric( + asList(circuitBreaker.getName(), "successful"), + metrics.getNumberOfSuccessfulCalls()); + + calls.addMetric( + asList(circuitBreaker.getName(), "failed"), + metrics.getNumberOfFailedCalls()); + + calls.addMetric( + asList(circuitBreaker.getName(), "not_permitted"), + metrics.getNumberOfNotPermittedCalls()); + + calls.addMetric( + asList(circuitBreaker.getName(), "buffered"), + metrics.getNumberOfBufferedCalls()); + + calls.addMetric( + asList(circuitBreaker.getName(), "buffered_max"), + metrics.getMaxNumberOfBufferedCalls()); + } + + return asList(calls, states); + } +} diff --git a/resilience4j-prometheus/src/main/java/io/github/resilience4j/prometheus/RateLimiterExports.java b/resilience4j-prometheus/src/main/java/io/github/resilience4j/prometheus/RateLimiterExports.java new file mode 100644 index 0000000000..c79b20b413 --- /dev/null +++ b/resilience4j-prometheus/src/main/java/io/github/resilience4j/prometheus/RateLimiterExports.java @@ -0,0 +1,136 @@ +/* + * + * Copyright 2017 Oleksandr Goldobin + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + * + * + */ +package io.github.resilience4j.prometheus; + +import io.github.resilience4j.ratelimiter.RateLimiter; +import io.github.resilience4j.ratelimiter.RateLimiterRegistry; +import io.prometheus.client.Collector; +import io.prometheus.client.GaugeMetricFamily; + +import java.util.List; +import java.util.function.Supplier; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Objects.requireNonNull; + +/** + * An adapter from builtin {@link RateLimiter.Metrics} to prometheus + * {@link io.prometheus.client.CollectorRegistry}. + */ +public class RateLimiterExports extends Collector { + private static final String DEFAULT_NAME = "rate_limiter"; + + private final String name; + private final Supplier> rateLimitersSupplier; + + /** + * Creates a new instance of {@link RateLimiterExports} with default metric name and + * {@link RateLimiterRegistry}. + * + * @param rateLimiterRegistry the rate limiter registry + */ + public RateLimiterExports(RateLimiterRegistry rateLimiterRegistry) { + this(rateLimiterRegistry::getAllRateLimiters); + } + + /** + * Creates a new instance of {@link RateLimiterExports} with default metric name and + * {@link Iterable} of rate limiters. + * + * @param rateLimiters the rate limiters + */ + public RateLimiterExports(Iterable rateLimiters) { + this(() -> rateLimiters); + } + + /** + * Creates a new instance of {@link RateLimiterExports} with default metric name and + * {@link Supplier} of rate limiters + * + * @param rateLimitersSupplier the supplier of rate limiters + */ + public RateLimiterExports(Supplier> rateLimitersSupplier) { + this(DEFAULT_NAME, rateLimitersSupplier); + } + + /** + * Creates a new instance of {@link RateLimiterExports} with specified metric name and + * {@link RateLimiterRegistry}. + * + * @param name the name of metric + * @param rateLimiterRegistry the rate limiter registry + */ + public RateLimiterExports(String name, RateLimiterRegistry rateLimiterRegistry) { + this(name, rateLimiterRegistry::getAllRateLimiters); + } + + /** + * Creates a new instance of {@link RateLimiterExports} with specified metric name and + * {@link Iterable} of rate limiters. + * + * @param name the name of metric + * @param rateLimiters the rate limiters + */ + public RateLimiterExports(String name, Iterable rateLimiters) { + this(name, () -> rateLimiters); + } + + /** + * Creates a new instance of {@link RateLimiterExports} with specified metric name and + * {@link Supplier} of rate limiters + * + * @param name the name of metric + * @param rateLimitersSupplier the supplier of rate limiters + */ + public RateLimiterExports(String name, Supplier> rateLimitersSupplier) { + requireNonNull(name); + requireNonNull(rateLimitersSupplier); + + this.name = name; + this.rateLimitersSupplier = rateLimitersSupplier; + } + + /** + * {@inheritDoc} + */ + @Override + public List collect() { + + final GaugeMetricFamily stats = new GaugeMetricFamily( + name, + "Rate Limiter Stats", + asList("name", "param")); + + for (RateLimiter rateLimiter : rateLimitersSupplier.get()) { + + final RateLimiter.Metrics metrics = rateLimiter.getMetrics(); + + stats.addMetric( + asList(rateLimiter.getName(), "available_permissions"), + metrics.getAvailablePermissions()); + + stats.addMetric( + asList(rateLimiter.getName(), "waiting_threads"), + metrics.getNumberOfWaitingThreads()); + } + + return singletonList(stats); + } +} diff --git a/resilience4j-prometheus/src/test/java/io/github/resilience4j/prometheus/CircuitBreakerExportsTest.java b/resilience4j-prometheus/src/test/java/io/github/resilience4j/prometheus/CircuitBreakerExportsTest.java new file mode 100644 index 0000000000..152464fe18 --- /dev/null +++ b/resilience4j-prometheus/src/test/java/io/github/resilience4j/prometheus/CircuitBreakerExportsTest.java @@ -0,0 +1,202 @@ +/* + * + * Copyright 2017 Oleksandr Goldobin + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + * + * + */ +package io.github.resilience4j.prometheus; + +import io.github.resilience4j.circuitbreaker.CircuitBreaker; +import io.github.resilience4j.circuitbreaker.CircuitBreakerOpenException; +import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry; +import io.github.resilience4j.circuitbreaker.internal.InMemoryCircuitBreakerRegistry; +import io.prometheus.client.CollectorRegistry; +import javaslang.Tuple; +import javaslang.collection.HashSet; +import javaslang.collection.HashMap; +import javaslang.collection.Map; +import org.junit.Test; + +import java.util.function.Supplier; + +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +public class CircuitBreakerExportsTest { + + @Test + public void testExportsCircuitBreakerStates() { + // Given + final CollectorRegistry registry = new CollectorRegistry(); + + final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("foo"); + + new CircuitBreakerExports("boo_circuit_breaker", singletonList(circuitBreaker)).register(registry); + + final Supplier> values = () -> HashSet + .of("closed", "open", "half_open") + .map(state -> + Tuple.of(state, registry.getSampleValue( + "boo_circuit_breaker_states", + new String[]{ "name", "state" }, + new String[]{ "foo", state}))) + .toMap(t -> t); + + // When + + final Map closedStateValues = values.get(); + + circuitBreaker.transitionToOpenState(); + + final Map openStateValues = values.get(); + + circuitBreaker.transitionToHalfOpenState(); + + final Map halfOpenStateValues = values.get(); + + circuitBreaker.transitionToClosedState(); + + final Map closedStateValues2 = values.get(); + + // Then + + assertThat(closedStateValues).isEqualTo(HashMap.of( + "closed", 1.0, + "open", 0.0, + "half_open", 0.0)); + + assertThat(openStateValues).isEqualTo(HashMap.of( + "closed", 0.0, + "open", 1.0, + "half_open", 0.0)); + + assertThat(halfOpenStateValues).isEqualTo(HashMap.of( + "closed", 0.0, + "open", 0.0, + "half_open", 1.0)); + + assertThat(closedStateValues2).isEqualTo(HashMap.of( + "closed", 1.0, + "open", 0.0, + "half_open", 0.0)); + } + + @Test + public void testExportsCircuitBreakerMetrics() { + // Given + final CollectorRegistry registry = new CollectorRegistry(); + + final CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("foo"); + + new CircuitBreakerExports("boo_circuit_breaker", singletonList(circuitBreaker)).register(registry); + + final Supplier> values = () -> HashSet + .of("successful", "failed", "not_permitted", "buffered", "buffered_max") + .map(callType -> + Tuple.of(callType, registry.getSampleValue( + "boo_circuit_breaker_calls", + new String[]{ "name", "call_result" }, + new String[]{ "foo", callType}))) + .toMap(t -> t); + + // When + + final Map initialValues = values.get(); + + circuitBreaker.executeRunnable(() -> {}); + + final Map afterSuccessValues = values.get(); + + try { + circuitBreaker.executeRunnable(() -> { + throw new SomeAppException("Some exception"); + }); + } catch (RuntimeException e) { + // expected + } + + final Map afterFailureValues = values.get(); + + circuitBreaker.transitionToOpenState(); + + try { + circuitBreaker.executeRunnable(() -> {}); + } catch (CircuitBreakerOpenException e) { + // expected + } + + final Map afterDeclinedValues = values.get(); + + // Then + + assertThat(initialValues).isEqualTo(HashMap.of( + "successful", 0.0, + "failed", 0.0, + "not_permitted", 0.0, + "buffered", 0.0, + "buffered_max", 100.0)); + + assertThat(afterSuccessValues).isEqualTo(HashMap.of( + "successful", 1.0, + "failed", 0.0, + "not_permitted", 0.0, + "buffered", 1.0, + "buffered_max", 100.0)); + + assertThat(afterFailureValues).isEqualTo(HashMap.of( + "successful", 1.0, + "failed", 1.0, + "not_permitted", 0.0, + "buffered", 2.0, + "buffered_max", 100.0)); + + assertThat(afterDeclinedValues).isEqualTo(HashMap.of( + "successful", 1.0, + "failed", 1.0, + "not_permitted", 1.0, + "buffered", 2.0, + "buffered_max", 100.0)); + } + + @Test + public void testConstructors() { + final CircuitBreakerRegistry registry = new InMemoryCircuitBreakerRegistry(); + + new CircuitBreakerExports("boo_breakers", singleton(CircuitBreaker.ofDefaults("foo"))); + new CircuitBreakerExports("boo_breakers", registry); + new CircuitBreakerExports("boo_breakers", () -> singleton(CircuitBreaker.ofDefaults("foo"))); + + new CircuitBreakerExports(singleton(CircuitBreaker.ofDefaults("foo"))); + new CircuitBreakerExports(registry); + new CircuitBreakerExports(() -> singleton(CircuitBreaker.ofDefaults("foo"))); + } + + @Test(expected = NullPointerException.class) + public void testConstructorWithNullName() { + new CircuitBreakerExports(null, () -> singleton(CircuitBreaker.ofDefaults("foo"))); + } + + @Test(expected = NullPointerException.class) + public void testConstructorWithNullSupplier() { + new CircuitBreakerExports("boo_breakers", (Supplier>) null); + } + + private static class SomeAppException extends RuntimeException { + SomeAppException(String message) { + super(message); + } + } +} diff --git a/resilience4j-prometheus/src/test/java/io/github/resilience4j/prometheus/RateLimiterExportsTest.java b/resilience4j-prometheus/src/test/java/io/github/resilience4j/prometheus/RateLimiterExportsTest.java new file mode 100644 index 0000000000..6437b5115a --- /dev/null +++ b/resilience4j-prometheus/src/test/java/io/github/resilience4j/prometheus/RateLimiterExportsTest.java @@ -0,0 +1,93 @@ +/* + * + * Copyright 2017 Oleksandr Goldobin + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + * + * + */ +package io.github.resilience4j.prometheus; + +import io.github.resilience4j.ratelimiter.RateLimiter; +import io.github.resilience4j.ratelimiter.RateLimiterConfig; +import io.github.resilience4j.ratelimiter.RateLimiterRegistry; +import io.github.resilience4j.ratelimiter.internal.InMemoryRateLimiterRegistry; +import io.prometheus.client.CollectorRegistry; +import javaslang.Tuple; +import javaslang.collection.HashMap; +import javaslang.collection.HashSet; +import javaslang.collection.Map; +import org.junit.Test; + +import java.util.function.Supplier; + +import static java.util.Collections.singleton; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +public class RateLimiterExportsTest { + + @Test + public void testExportsRateLimiterMetrics() { + // Given + final CollectorRegistry registry = new CollectorRegistry(); + + final RateLimiter rateLimiter = RateLimiter.ofDefaults("foo"); + + new RateLimiterExports("boo_rate_limiter", singletonList(rateLimiter)).register(registry); + + final Supplier> values = () -> HashSet + .of("available_permissions", "waiting_threads") + .map(param -> + Tuple.of(param, registry.getSampleValue( + "boo_rate_limiter", + new String[]{"name", "param"}, + new String[]{"foo", param}))) + .toMap(t -> t); + + // When + + final Map initialValues = values.get(); + + // Then + + assertThat(initialValues).isEqualTo(HashMap.of( + "available_permissions", 50.0, + "waiting_threads", 0.0 + )); + } + + + @Test + public void testConstructors() { + final RateLimiterRegistry registry = new InMemoryRateLimiterRegistry(RateLimiterConfig.ofDefaults()); + + new RateLimiterExports("boo_limiters", singleton(RateLimiter.ofDefaults("foo"))); + new RateLimiterExports("boo_limiters", registry); + new RateLimiterExports("boo_limiters", () -> singleton(RateLimiter.ofDefaults("foo"))); + + new RateLimiterExports(singleton(RateLimiter.ofDefaults("foo"))); + new RateLimiterExports(registry); + new RateLimiterExports(() -> singleton(RateLimiter.ofDefaults("foo"))); + } + + @Test(expected = NullPointerException.class) + public void testConstructorWithNullName() { + new RateLimiterExports(null, () -> singleton(RateLimiter.ofDefaults("foo"))); + } + + @Test(expected = NullPointerException.class) + public void testConstructorWithNullSupplier() { + new RateLimiterExports("boo_limiters", (Supplier>) null); + } +} diff --git a/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/RateLimiterRegistry.java b/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/RateLimiterRegistry.java index a536970030..30817fd73d 100644 --- a/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/RateLimiterRegistry.java +++ b/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/RateLimiterRegistry.java @@ -19,6 +19,7 @@ package io.github.resilience4j.ratelimiter; import io.github.resilience4j.ratelimiter.internal.InMemoryRateLimiterRegistry; +import javaslang.collection.Seq; import java.util.function.Supplier; @@ -27,6 +28,13 @@ */ public interface RateLimiterRegistry { + /** + * Returns all managed {@link RateLimiter} instances. + * + * @return all managed {@link RateLimiter} instances. + */ + Seq getAllRateLimiters(); + /** * Returns a managed {@link RateLimiter} or creates a new one with the default RateLimiter configuration. * diff --git a/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/internal/InMemoryRateLimiterRegistry.java b/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/internal/InMemoryRateLimiterRegistry.java index e7db8ab669..72a36a4db3 100644 --- a/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/internal/InMemoryRateLimiterRegistry.java +++ b/resilience4j-ratelimiter/src/main/java/io/github/resilience4j/ratelimiter/internal/InMemoryRateLimiterRegistry.java @@ -23,6 +23,8 @@ import io.github.resilience4j.ratelimiter.RateLimiter; import io.github.resilience4j.ratelimiter.RateLimiterConfig; import io.github.resilience4j.ratelimiter.RateLimiterRegistry; +import javaslang.collection.Array; +import javaslang.collection.Seq; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -49,6 +51,14 @@ public InMemoryRateLimiterRegistry(final RateLimiterConfig defaultRateLimiterCon rateLimiters = new ConcurrentHashMap<>(); } + /** + * {@inheritDoc} + */ + @Override + public Seq getAllRateLimiters() { + return Array.ofAll(rateLimiters.values()); + } + /** * {@inheritDoc} */ diff --git a/resilience4j-ratelimiter/src/test/java/io/github/resilience4j/ratelimiter/internal/InMemoryRateLimiterRegistryTest.java b/resilience4j-ratelimiter/src/test/java/io/github/resilience4j/ratelimiter/internal/InMemoryRateLimiterRegistryTest.java index ed165bc60c..6ed7aaa820 100644 --- a/resilience4j-ratelimiter/src/test/java/io/github/resilience4j/ratelimiter/internal/InMemoryRateLimiterRegistryTest.java +++ b/resilience4j-ratelimiter/src/test/java/io/github/resilience4j/ratelimiter/internal/InMemoryRateLimiterRegistryTest.java @@ -19,6 +19,7 @@ package io.github.resilience4j.ratelimiter.internal; import static org.assertj.core.api.BDDAssertions.then; +import static org.assertj.core.api.Java6Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -133,4 +134,13 @@ public void rateLimiterNewWithNullConfigSupplier() throws Exception { Supplier rateLimiterConfigSupplier = null; registry.rateLimiter("name", rateLimiterConfigSupplier); } + + @Test + public void rateLimiterGetAllRateLimiters() { + RateLimiterRegistry registry = new InMemoryRateLimiterRegistry(config); + registry.rateLimiter("foo"); + + assertThat(registry.getAllRateLimiters().size()).isEqualTo(1); + assertThat(registry.getAllRateLimiters().get(0).getName()).isEqualTo("foo"); + } } \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index e74fe26821..daa90afa4a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,4 +13,5 @@ include 'resilience4j-consumer' include 'resilience4j-vertx' include 'resilience4j-spring-boot' include 'resilience4j-retrofit' +include 'resilience4j-prometheus'