Skip to content

Commit

Permalink
Merge pull request ReactiveX#88 from resilience4j/ratelimiter_metrics
Browse files Browse the repository at this point in the history
Issue ReactiveX#72: rate limiter metrics initial
  • Loading branch information
storozhukBM committed Apr 3, 2017
2 parents 60b1a01 + e2a2a07 commit 750862b
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 28 deletions.
2 changes: 2 additions & 0 deletions resilience4j-metrics/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
dependencies {
compile (libraries.metrics)
compileOnly project(':resilience4j-circuitbreaker')
compileOnly project(':resilience4j-ratelimiter')
testCompile project(':resilience4j-test')
testCompile project(':resilience4j-circuitbreaker')
testCompile project(':resilience4j-ratelimiter')
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,48 +18,47 @@
*/
package io.github.resilience4j.metrics;

import static com.codahale.metrics.MetricRegistry.name;
import static java.util.Objects.requireNonNull;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricSet;

import java.util.Map;

import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import javaslang.collection.Array;

import static com.codahale.metrics.MetricRegistry.name;
import static java.util.Objects.requireNonNull;
import java.util.Map;

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

private final MetricRegistry metricRegistry = new MetricRegistry();
private static final String DEFAULT_PREFIX = "resilience4j.circuitbreaker";
private final MetricRegistry metricRegistry = new MetricRegistry();

private CircuitBreakerMetrics(Iterable<CircuitBreaker> circuitBreakers){
private CircuitBreakerMetrics(Iterable<CircuitBreaker> circuitBreakers) {
this(DEFAULT_PREFIX, circuitBreakers);
}

private CircuitBreakerMetrics(String prefix, Iterable<CircuitBreaker> circuitBreakers){
private CircuitBreakerMetrics(String prefix, Iterable<CircuitBreaker> circuitBreakers) {
requireNonNull(prefix);
requireNonNull(circuitBreakers);
circuitBreakers.forEach(circuitBreaker -> {
String name = circuitBreaker.getName();
CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();
String name = circuitBreaker.getName();
CircuitBreaker.Metrics metrics = circuitBreaker.getMetrics();

metricRegistry.register(name(prefix, name, "successful"),
metricRegistry.register(name(prefix, name, "successful"),
(Gauge<Integer>) metrics::getNumberOfSuccessfulCalls);
metricRegistry.register(name(prefix, name, "failed"),
metricRegistry.register(name(prefix, name, "failed"),
(Gauge<Integer>) metrics::getNumberOfFailedCalls);
metricRegistry.register(name(prefix, name, "not_permitted"),
metricRegistry.register(name(prefix, name, "not_permitted"),
(Gauge<Long>) metrics::getNumberOfNotPermittedCalls);
metricRegistry.register(name(prefix, name, "buffered"),
metricRegistry.register(name(prefix, name, "buffered"),
(Gauge<Integer>) metrics::getNumberOfBufferedCalls);
metricRegistry.register(name(prefix, name, "buffered_max"),
metricRegistry.register(name(prefix, name, "buffered_max"),
(Gauge<Integer>) metrics::getMaxNumberOfBufferedCalls);
}
);
Expand All @@ -69,7 +68,7 @@ private CircuitBreakerMetrics(String prefix, Iterable<CircuitBreaker> circuitBre
* Creates a new instance CircuitBreakerMetrics {@link CircuitBreakerMetrics} with specified metrics names prefix and
* a {@link CircuitBreakerRegistry} as a source.
*
* @param prefix the prefix of metrics names
* @param prefix the prefix of metrics names
* @param circuitBreakerRegistry the registry of circuit breakers
*/
public static CircuitBreakerMetrics ofCircuitBreakerRegistry(String prefix, CircuitBreakerRegistry circuitBreakerRegistry) {
Expand All @@ -79,7 +78,7 @@ public static CircuitBreakerMetrics ofCircuitBreakerRegistry(String prefix, Circ
/**
* Creates a new instance CircuitBreakerMetrics {@link CircuitBreakerMetrics} with
* a {@link CircuitBreakerRegistry} as a source.
*
* @param circuitBreakerRegistry the registry of circuit breakers
*/
public static CircuitBreakerMetrics ofCircuitBreakerRegistry(CircuitBreakerRegistry circuitBreakerRegistry) {
Expand All @@ -98,12 +97,12 @@ public static CircuitBreakerMetrics ofIterable(Iterable<CircuitBreaker> circuitB

/**
* Creates a new instance CircuitBreakerMetrics {@link CircuitBreakerMetrics} with
* an {@link Iterable} if circuit breakers as a source.
* an {@link Iterable} of circuit breakers as a source.
*
* @param circuitBreakers the circuit breakers
*/
public static CircuitBreakerMetrics ofIterable(String prefix, Iterable<CircuitBreaker> circuitBreakers) {
return new CircuitBreakerMetrics(circuitBreakers);
return new CircuitBreakerMetrics(prefix, circuitBreakers);
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
*
* Copyright 2017: Bohdan Storozhuk
*
* 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.metrics;

import static com.codahale.metrics.MetricRegistry.name;
import static java.util.Objects.requireNonNull;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.MetricSet;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import javaslang.collection.Array;

import java.util.Map;

/**
* An adapter which exports {@link RateLimiter.Metrics} as Dropwizard Metrics Gauges.
*/
public class RateLimiterMetrics implements MetricSet {

private static final String PREFIX_NULL = "Prefix must not be null";
private static final String ITERABLE_NULL = "RateLimiters iterable must not be null";

private static final String DEFAULT_PREFIX = "resilience4j.ratelimiter";

private final MetricRegistry metricRegistry = new MetricRegistry();

private RateLimiterMetrics(Iterable<RateLimiter> rateLimiters) {
this(DEFAULT_PREFIX, rateLimiters);
}

private RateLimiterMetrics(String prefix, Iterable<RateLimiter> rateLimiters) {
requireNonNull(prefix, PREFIX_NULL);
requireNonNull(rateLimiters, ITERABLE_NULL);

rateLimiters.forEach(rateLimiter -> {
String name = rateLimiter.getName();
RateLimiter.Metrics metrics = rateLimiter.getMetrics();

metricRegistry.register(name(prefix, name, "number_of_waiting_threads"),
(Gauge<Integer>) metrics::getNumberOfWaitingThreads);
metricRegistry.register(name(prefix, name, "available_permissions"),
(Gauge<Integer>) metrics::getAvailablePermissions);
}
);
}

/**
* Creates a new instance {@link RateLimiterMetrics} with specified metrics names prefix and
* a {@link RateLimiterRegistry} as a source.
*
* @param prefix the prefix of metrics names
* @param rateLimiterRegistry the registry of rate limiters
*/
public static RateLimiterMetrics ofRateLimiterRegistry(String prefix, RateLimiterRegistry rateLimiterRegistry) {
return new RateLimiterMetrics(prefix, rateLimiterRegistry.getAllRateLimiters());
}

/**
* Creates a new instance {@link RateLimiterMetrics} with
* a {@link RateLimiterRegistry} as a source.
*
* @param rateLimiterRegistry the registry of rate limiters
*/
public static RateLimiterMetrics ofRateLimiterRegistry(RateLimiterRegistry rateLimiterRegistry) {
return new RateLimiterMetrics(rateLimiterRegistry.getAllRateLimiters());
}

/**
* Creates a new instance {@link RateLimiterMetrics} with
* an {@link Iterable} of rate limiters as a source.
*
* @param rateLimiters the rate limiters
*/
public static RateLimiterMetrics ofIterable(Iterable<RateLimiter> rateLimiters) {
return new RateLimiterMetrics(rateLimiters);
}

/**
* Creates a new instance {@link RateLimiterMetrics} with
* an {@link Iterable} of rate limiters as a source.
*
* @param rateLimiters the rate limiters
*/
public static RateLimiterMetrics ofIterable(String prefix, Iterable<RateLimiter> rateLimiters) {
return new RateLimiterMetrics(prefix, rateLimiters);
}


/**
* Creates a new instance of {@link RateLimiterMetrics} with a rate limiter as a source.
*
* @param rateLimiter the rate limiter
*/
public static RateLimiterMetrics ofRateLimiter(RateLimiter rateLimiter) {
return new RateLimiterMetrics(Array.of(rateLimiter));
}

@Override
public Map<String, Metric> getMetrics() {
return metricRegistry.getMetrics();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public void shouldDecorateCheckedSupplier() throws Throwable {

String value = timedSupplier.get();

// Then the counter ofCircuitBreakerRegistry metrics should be one
// Then the counter ofRateLimiterRegistry metrics should be one
assertThat(timer.getCount()).isEqualTo(1);

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

timedRunnable.run();

// Then the counter ofCircuitBreakerRegistry metrics should be one
// Then the counter ofRateLimiterRegistry metrics should be one
assertThat(timer.getCount()).isEqualTo(1);

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

timedRunnable.run();

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

// Then the counter ofCircuitBreakerRegistry metrics should be one
// Then the counter ofRateLimiterRegistry metrics should be one
assertThat(timer.getCount()).isEqualTo(1);

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

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

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

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

// Then the counter ofCircuitBreakerRegistry metrics should be ten
// Then the counter ofRateLimiterRegistry metrics should be ten
assertThat(timer.getCount()).isEqualTo(1);
// Then the helloWorldService should be invoked 1 time
BDDMockito.then(helloWorldService).should(Mockito.times(1)).returnHelloWorldWithNameWithException("Tom");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
*
* Copyright 2017: Robert Winkler
*
* 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.metrics;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;

import com.codahale.metrics.MetricRegistry;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterConfig;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import io.github.resilience4j.test.HelloWorldService;
import org.junit.Before;
import org.junit.Test;
import org.mockito.BDDMockito;

public class RateLimiterMetricsTest {

private static final int DEFAULT_LIMIT_FOR_PERIOD = RateLimiterConfig.ofDefaults().getLimitForPeriod();
private MetricRegistry metricRegistry;
private HelloWorldService helloWorldService;

@Before
public void setUp() {
metricRegistry = new MetricRegistry();
helloWorldService = mock(HelloWorldService.class);
}

@Test
public void shouldRegisterMetrics() throws Throwable {
//Given
RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.ofDefaults();
RateLimiter rateLimiter = rateLimiterRegistry.rateLimiter("testLimit");
metricRegistry.registerAll(RateLimiterMetrics.ofRateLimiterRegistry(rateLimiterRegistry));

// Given the HelloWorldService returns Hello world
BDDMockito.given(helloWorldService.returnHelloWorld()).willReturn("Hello world");

//When
String value = rateLimiter.executeSupplier(helloWorldService::returnHelloWorld);

//Then
assertThat(value).isEqualTo("Hello world");
// Then the helloWorldService should be invoked 1 time
BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld();
assertThat(metricRegistry.getMetrics()).hasSize(2);
assertThat(metricRegistry.getGauges().get("resilience4j.ratelimiter.testLimit.number_of_waiting_threads")
.getValue()).isEqualTo(0);
assertThat(metricRegistry.getGauges().get("resilience4j.ratelimiter.testLimit.available_permissions").getValue())
.isIn(DEFAULT_LIMIT_FOR_PERIOD, DEFAULT_LIMIT_FOR_PERIOD - 1);
}

@Test
public void shouldUseCustomPrefix() throws Throwable {
//Given
RateLimiterRegistry rateLimiterRegistry = RateLimiterRegistry.ofDefaults();
RateLimiter rateLimiter = rateLimiterRegistry.rateLimiter("testLimit");
metricRegistry.registerAll(RateLimiterMetrics.ofIterable("testPre", rateLimiterRegistry.getAllRateLimiters()));

// Given the HelloWorldService returns Hello world
BDDMockito.given(helloWorldService.returnHelloWorld()).willReturn("Hello world");

//When
String value = rateLimiter.executeSupplier(helloWorldService::returnHelloWorld);

//Then
assertThat(value).isEqualTo("Hello world");
// Then the helloWorldService should be invoked 1 time
BDDMockito.then(helloWorldService).should(times(1)).returnHelloWorld();
assertThat(metricRegistry.getMetrics()).hasSize(2);
assertThat(metricRegistry.getGauges().get("testPre.testLimit.number_of_waiting_threads")
.getValue()).isEqualTo(0);
assertThat(metricRegistry.getGauges().get("testPre.testLimit.available_permissions").getValue())
.isIn(DEFAULT_LIMIT_FOR_PERIOD, DEFAULT_LIMIT_FOR_PERIOD - 1);
}
}
Loading

0 comments on commit 750862b

Please sign in to comment.