Skip to content

Commit

Permalink
Issue ReactiveX#72: Added retry metrics to resilience4j-metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrew From authored and RobWin committed Apr 5, 2017
1 parent 20d2e97 commit 6a98403
Show file tree
Hide file tree
Showing 13 changed files with 345 additions and 41 deletions.
4 changes: 4 additions & 0 deletions resilience4j-metrics/build.gradle
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
dependencies {
compile (libraries.metrics)
compileOnly project(':resilience4j-circuitbreaker')
compileOnly project(':resilience4j-retry')
compileOnly project(':resilience4j-ratelimiter')
testCompile project(':resilience4j-test')
testCompile project(':resilience4j-circuitbreaker')
testCompile project(':resilience4j-ratelimiter')
testCompile project(':resilience4j-retry')
testCompile project(':resilience4j-test')
testCompile project(':resilience4j-circuitbreaker')
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.github.resilience4j.metrics;

import com.codahale.metrics.*;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryRegistry;
import javaslang.collection.Array;

import java.util.Map;

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

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

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

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

private RetryMetrics(String prefix, Iterable<Retry> retries){
requireNonNull(prefix);
requireNonNull(retries);
retries.forEach(retry -> {
String name = retry.getName();
Retry.Metrics metrics = retry.getMetrics();

metricRegistry.register(name(prefix, name, "retry_max_ratio"),
new RetryRatio(metrics.getNumAttempts(), metrics.getMaxAttempts()));

});
}

public static RetryMetrics ofRateLimiterRegistry(String prefix, RetryRegistry retryRegistry) {
return new RetryMetrics(prefix, retryRegistry.getAllRetries());
}

public static RetryMetrics ofRateLimiterRegistry(RetryRegistry retryRegistry) {
return new RetryMetrics(retryRegistry.getAllRetries());
}

public static RetryMetrics ofIterable(String prefix, Iterable<Retry> retries) {
return new RetryMetrics(prefix, retries);
}

public static RetryMetrics ofIterable(Iterable<Retry> retries) {
return new RetryMetrics(retries);
}

public static RetryMetrics ofRateLimiter(Retry retry) {
return new RetryMetrics(Array.of(retry));
}

@Override
public Map<String, Metric> getMetrics() {
return metricRegistry.getMetrics();
}

/**
* Implements a {@link RatioGauge} that represents the ratio between attempts made, and the maximum allowed retry attempts.
*/
private final class RetryRatio extends RatioGauge {

private double numAttempts;

private double maxAttempts;

public RetryRatio(int numAttempts, int maxAttempts) {
this.numAttempts = (double) numAttempts;
this.maxAttempts = (double) maxAttempts;
}

@Override
protected Ratio getRatio() {
return Ratio.of(numAttempts, maxAttempts);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.github.resilience4j.metrics;

import com.codahale.metrics.MetricRegistry;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.retry.Retry;
import io.github.resilience4j.retry.RetryRegistry;
import io.github.resilience4j.test.HelloWorldService;
import javaslang.control.Try;
import org.junit.Before;
import org.junit.Test;
import org.mockito.BDDMockito;

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

public class RetryMetricsTest {

private MetricRegistry metricRegistry;
private HelloWorldService helloWorldService;

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

@Test
public void shouldRegisterMetrics() throws Throwable {
//Given
RetryRegistry retryRegistry = RetryRegistry.ofDefaults();
Retry retry = retryRegistry.retry("testName");
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");
metricRegistry.registerAll(RetryMetrics.ofRateLimiterRegistry(retryRegistry));

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

// Setup circuitbreaker with retry
Try.CheckedSupplier<String> decoratedSupplier = CircuitBreaker
.decorateCheckedSupplier(circuitBreaker, helloWorldService::returnHelloWorld);
decoratedSupplier = Retry
.decorateCheckedSupplier(retry, decoratedSupplier);


//When
String value = Try.of(decoratedSupplier)
.recover(throwable -> "Hello from Recovery").get();

//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(1);
assertThat(metricRegistry.getGauges().get("resilience4j.retry.testName.retry_max_ratio").getValue()).isEqualTo(0.0);
}

@Test
public void shouldUseCustomPrefix() throws Throwable {
//Given
RetryRegistry retryRegistry = RetryRegistry.ofDefaults();
Retry retry = retryRegistry.retry("testName");
CircuitBreakerRegistry circuitBreakerRegistry = CircuitBreakerRegistry.ofDefaults();
CircuitBreaker circuitBreaker = circuitBreakerRegistry.circuitBreaker("testName");
metricRegistry.registerAll(RetryMetrics.ofRateLimiterRegistry("testPrefix",retryRegistry));

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

// Setup circuitbreaker with retry
Try.CheckedSupplier<String> decoratedSupplier = CircuitBreaker
.decorateCheckedSupplier(circuitBreaker, helloWorldService::returnHelloWorld);
decoratedSupplier = Retry
.decorateCheckedSupplier(retry, decoratedSupplier);

//When
String value = Try.of(decoratedSupplier)
.recover(throwable -> "Hello from Recovery").get();

//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(1);
assertThat(metricRegistry.getGauges().get("testPrefix.testName.retry_max_ratio").getValue()).isEqualTo(0.0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public interface AsyncRetry {
*
* @return the ID of this Retry
*/
String getId();
String getName();

/**
* Records a successful call.
Expand Down Expand Up @@ -95,6 +95,29 @@ static <T> Supplier<CompletionStage<T>> decorateCompletionStage(
return promise;
};
}

/**
* Get the Metrics of this RateLimiter.
*
* @return the Metrics of this RateLimiter
*/
Metrics getMetrics();

interface Metrics {
/**
* Returns how many attempts this have been made by this retry.
*
* @return how many retries have been attempted, but failed.
*/
int getNumAttempts();

/**
* Returns how many retry attempts are allowed before failure.
*
* @return how many retries are allowed before failure.
*/
int getMaxAttempts();
}
}

class AsyncRetryBlock<T> implements Runnable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public interface Retry {
*
* @return the ID of this Retry
*/
String getId();
String getName();

/**
* Records a successful call.
Expand Down Expand Up @@ -66,35 +66,35 @@ public interface Retry {
/**
* Creates a Retry with a custom Retry configuration.
*
* @param id the ID of the Retry
* @param name the ID of the Retry
* @param retryConfig a custom Retry configuration
*
* @return a Retry with a custom Retry configuration.
*/
static RetryContext of(String id, RetryConfig retryConfig){
return new RetryContext(id, retryConfig);
static RetryContext of(String name, RetryConfig retryConfig){
return new RetryContext(name, retryConfig);
}

/**
* Creates a Retry with a custom Retry configuration.
*
* @param id the ID of the Retry
* @param name the ID of the Retry
* @param retryConfigSupplier a supplier of a custom Retry configuration
*
* @return a Retry with a custom Retry configuration.
*/
static RetryContext of(String id, Supplier<RetryConfig> retryConfigSupplier){
return new RetryContext(id, retryConfigSupplier.get());
static RetryContext of(String name, Supplier<RetryConfig> retryConfigSupplier){
return new RetryContext(name, retryConfigSupplier.get());
}

/**
* Creates a Retry with default configuration.
*
* @param id the ID of the Retry
* @param name the ID of the Retry
* @return a Retry with default configuration
*/
static Retry ofDefaults(String id){
return new RetryContext(id, RetryConfig.ofDefaults());
static Retry ofDefaults(String name){
return new RetryContext(name, RetryConfig.ofDefaults());
}

/**
Expand Down Expand Up @@ -277,4 +277,26 @@ static <T, R> Function<T, R> decorateFunction(Retry retryContext, Function<T, R>
};
}

/**
* Get the Metrics of this RateLimiter.
*
* @return the Metrics of this RateLimiter
*/
Metrics getMetrics();

interface Metrics {
/**
* Returns how many attempts this have been made by this retry.
*
* @return how many retries have been attempted, but failed.
*/
int getNumAttempts();

/**
* Returns how many retry attempts are allowed before failure.
*
* @return how many retries are allowed before failure.
*/
int getMaxAttempts();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,21 @@

abstract class AbstractRetryEvent implements RetryEvent {

private final String id;
private final String name;
private final ZonedDateTime creationTime;
private final int numberOfAttempts;
private final Throwable lastThrowable;

AbstractRetryEvent(String id, int numberOfAttempts, Throwable lastThrowable) {
this.id = id;
AbstractRetryEvent(String name, int numberOfAttempts, Throwable lastThrowable) {
this.name = name;
this.numberOfAttempts = numberOfAttempts;
this.creationTime = ZonedDateTime.now();
this.lastThrowable = lastThrowable;
}

@Override
public String getId() {
return id;
public String getName() {
return name;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public interface RetryEvent {
*
* @return the ID of the Retry
*/
String getId();
String getName();

/**
* Returns the number of attempts.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
*/
public class RetryOnErrorEvent extends AbstractRetryEvent {

public RetryOnErrorEvent(String id, int numberOfAttempts, Throwable lastThrowable) {
super(id, numberOfAttempts, lastThrowable);
public RetryOnErrorEvent(String name, int numberOfAttempts, Throwable lastThrowable) {
super(name, numberOfAttempts, lastThrowable);
}
@Override
public Type getEventType() {
Expand All @@ -35,7 +35,7 @@ public Type getEventType() {
public String toString() {
return String.format("%s: Retry '%s' recorded a failed retry attempt. Number of retry attempts: '%d', Last exception was: '%s'.",
getCreationTime(),
getId(),
getName(),
getNumberOfAttempts(),
getLastThrowable().toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@
*/
public class RetryOnSuccessEvent extends AbstractRetryEvent {

public RetryOnSuccessEvent(String id, int currentNumOfAttempts, Throwable lastThrowable) {
super(id, currentNumOfAttempts, lastThrowable);
public RetryOnSuccessEvent(String name, int currentNumOfAttempts, Throwable lastThrowable) {
super(name, currentNumOfAttempts, lastThrowable);
}

@Override
Expand All @@ -36,7 +36,7 @@ public Type getEventType() {
public String toString() {
return String.format("%s: Retry '%s' recorded a successful retry attempt. Number of retry attempts: '%d', Last exception was: '%s'.",
getCreationTime(),
getId(),
getName(),
getNumberOfAttempts(),
getLastThrowable().toString());
}
Expand Down
Loading

0 comments on commit 6a98403

Please sign in to comment.