Skip to content

Commit

Permalink
Issue ReactiveX#531: Added Dropwizard metrics for TimeLimiter (Reacti…
Browse files Browse the repository at this point in the history
  • Loading branch information
hexmind authored and RobWin committed Aug 30, 2019
1 parent bb1ebb4 commit a56eb47
Show file tree
Hide file tree
Showing 15 changed files with 849 additions and 21 deletions.
2 changes: 2 additions & 0 deletions resilience4j-metrics/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ dependencies {
compileOnly project(':resilience4j-circuitbreaker')
compileOnly project(':resilience4j-retry')
compileOnly project(':resilience4j-ratelimiter')
compileOnly project(':resilience4j-timelimiter')
testCompile project(':resilience4j-test')
testCompile project(':resilience4j-bulkhead')
testCompile project(':resilience4j-circuitbreaker')
testCompile project(':resilience4j-ratelimiter')
testCompile project(':resilience4j-timelimiter')
testCompile project(':resilience4j-retry')
testCompile project(':resilience4j-test')
testCompile project(':resilience4j-circuitbreaker')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/*
*
* Copyright 2019 authors
*
* 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 io.github.resilience4j.timelimiter.TimeLimiter;
import io.github.resilience4j.timelimiter.TimeLimiterRegistry;
import io.vavr.collection.Array;

import java.util.Map;

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

import static com.codahale.metrics.MetricRegistry.name;
import static io.github.resilience4j.timelimiter.utils.MetricNames.DEFAULT_PREFIX;
import static io.github.resilience4j.timelimiter.utils.MetricNames.FAILED;
import static io.github.resilience4j.timelimiter.utils.MetricNames.SUCCESSFUL;
import static io.github.resilience4j.timelimiter.utils.MetricNames.TIMEOUT;
import static java.util.Objects.requireNonNull;

/**
* An adapter which exports TimeLimiter's events as Dropwizard Metrics.
*/
public class TimeLimiterMetrics implements MetricSet {

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

private final MetricRegistry metricRegistry;

private TimeLimiterMetrics(Iterable<TimeLimiter> timeLimiters) {
this(DEFAULT_PREFIX, timeLimiters, new MetricRegistry());
}

private TimeLimiterMetrics(String prefix, Iterable<TimeLimiter> timeLimiters, MetricRegistry metricRegistry) {
requireNonNull(prefix, PREFIX_NULL);
requireNonNull(timeLimiters, ITERABLE_NULL);
requireNonNull(metricRegistry);
this.metricRegistry = metricRegistry;
timeLimiters.forEach(timeLimiter -> {
String name = timeLimiter.getName();
Counter successes = metricRegistry.counter(name(prefix, name, SUCCESSFUL));
Counter failures = metricRegistry.counter(name(prefix, name, FAILED));
Counter timeouts = metricRegistry.counter(name(prefix, name, TIMEOUT));
timeLimiter.getEventPublisher().onSuccess(event -> successes.inc());
timeLimiter.getEventPublisher().onError(event -> failures.inc());
timeLimiter.getEventPublisher().onTimeout(event -> timeouts.inc());
}
);
}

/**
* Creates a new instance {@link TimeLimiterMetrics} with specified metrics names prefix and
* a {@link TimeLimiterRegistry} as a source.
*
* @param prefix the prefix of metrics names
* @param timeLimiterRegistry the registry of time limiters
* @param metricRegistry the metric registry
*/
public static TimeLimiterMetrics ofTimeLimiterRegistry(String prefix, TimeLimiterRegistry timeLimiterRegistry, MetricRegistry metricRegistry) {
return new TimeLimiterMetrics(prefix, timeLimiterRegistry.getAllTimeLimiters(), metricRegistry);
}

/**
* Creates a new instance {@link TimeLimiterMetrics} with specified metrics names prefix and
* a {@link TimeLimiterRegistry} as a source.
*
* @param prefix the prefix of metrics names
* @param timeLimiterRegistry the registry of time limiters
*/
public static TimeLimiterMetrics ofTimeLimiterRegistry(String prefix, TimeLimiterRegistry timeLimiterRegistry) {
return new TimeLimiterMetrics(prefix, timeLimiterRegistry.getAllTimeLimiters(), new MetricRegistry());
}

/**
* Creates a new instance {@link TimeLimiterMetrics} with
* a {@link TimeLimiterRegistry} as a source.
*
* @param timeLimiterRegistry the registry of time limiters
* @param metricRegistry the metric registry
*/
public static TimeLimiterMetrics ofTimeLimiterRegistry(TimeLimiterRegistry timeLimiterRegistry, MetricRegistry metricRegistry) {
return new TimeLimiterMetrics(DEFAULT_PREFIX, timeLimiterRegistry.getAllTimeLimiters(), metricRegistry);
}

/**
* Creates a new instance {@link TimeLimiterMetrics} with
* a {@link TimeLimiterRegistry} as a source.
*
* @param timeLimiterRegistry the registry of time limiters
*/
public static TimeLimiterMetrics ofTimeLimiterRegistry(TimeLimiterRegistry timeLimiterRegistry) {
return new TimeLimiterMetrics(timeLimiterRegistry.getAllTimeLimiters());
}

/**
* Creates a new instance {@link TimeLimiterMetrics} with
* an {@link Iterable} of time limiters as a source.
*
* @param timeLimiters the time limiters
*/
public static TimeLimiterMetrics ofIterable(Iterable<TimeLimiter> timeLimiters) {
return new TimeLimiterMetrics(timeLimiters);
}

/**
* Creates a new instance {@link TimeLimiterMetrics} with
* an {@link Iterable} of time limiters as a source.
*
* @param timeLimiters the time limiters
*/
public static TimeLimiterMetrics ofIterable(String prefix, Iterable<TimeLimiter> timeLimiters) {
return new TimeLimiterMetrics(prefix, timeLimiters, new MetricRegistry());
}


/**
* Creates a new instance of {@link TimeLimiterMetrics} with a time limiter as a source.
*
* @param timeLimiter the time limiter
*/
public static TimeLimiterMetrics ofTimeLimiter(TimeLimiter timeLimiter) {
return new TimeLimiterMetrics(Array.of(timeLimiter));
}

@Override
public Map<String, Metric> getMetrics() {
return metricRegistry.getMetrics();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
/*
*
* Copyright 2019 authors
*
* 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 io.github.resilience4j.timelimiter.TimeLimiter;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;
import io.github.resilience4j.timelimiter.TimeLimiterRegistry;
import io.vavr.control.Try;

import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;

import org.junit.Before;
import org.junit.Test;

import com.codahale.metrics.MetricRegistry;

import static io.github.resilience4j.metrics.assertion.MetricRegistryAssert.assertThat;
import static org.assertj.core.api.BDDAssertions.then;

public class TimeLimiterMetricsTest {

private static final String DEFAULT_PREFIX = "resilience4j.timelimiter.UNDEFINED.";
private static final String SUCCESSFUL = "successful";
private static final String FAILED = "failed";
private static final String TIMEOUT = "timeout";

private MetricRegistry metricRegistry;

@Before
public void setUp() {
metricRegistry = new MetricRegistry();
}

@Test
public void shouldRegisterMetrics() throws Exception {
TimeLimiterRegistry timeLimiterRegistry = TimeLimiterRegistry.ofDefaults();
TimeLimiter timeLimiter = timeLimiterRegistry.timeLimiter("testLimit");
metricRegistry.registerAll(TimeLimiterMetrics.ofTimeLimiterRegistry(timeLimiterRegistry));
String expectedPrefix = "resilience4j.timelimiter.testLimit.";
Supplier<CompletableFuture<String>> futureSupplier = () ->
CompletableFuture.completedFuture("Hello world");

String result = timeLimiter.decorateFutureSupplier(futureSupplier).call();

then(result).isEqualTo("Hello world");
assertThat(metricRegistry).hasMetricsSize(3);
assertThat(metricRegistry).counter(expectedPrefix + SUCCESSFUL)
.hasValue(1L);
assertThat(metricRegistry).counter(expectedPrefix + FAILED)
.hasValue(0L);
assertThat(metricRegistry).counter(expectedPrefix + TIMEOUT)
.hasValue(0L);
}

@Test
public void shouldUseCustomPrefix() throws Exception {
TimeLimiterRegistry timeLimiterRegistry = TimeLimiterRegistry.ofDefaults();
TimeLimiter timeLimiter = timeLimiterRegistry.timeLimiter("testLimit");
metricRegistry.registerAll(TimeLimiterMetrics.ofIterable("testPre", timeLimiterRegistry.getAllTimeLimiters()));
String expectedPrefix = "testPre.testLimit.";
Supplier<CompletableFuture<String>> futureSupplier = () ->
CompletableFuture.completedFuture("Hello world");

String result = timeLimiter.decorateFutureSupplier(futureSupplier).call();

then(result).isEqualTo("Hello world");
assertThat(metricRegistry).hasMetricsSize(3);
assertThat(metricRegistry).counter(expectedPrefix + SUCCESSFUL)
.hasValue(1L);
assertThat(metricRegistry).counter(expectedPrefix + FAILED)
.hasValue(0L);
assertThat(metricRegistry).counter(expectedPrefix + TIMEOUT)
.hasValue(0L);
}

@Test
public void shouldRecordSuccesses() {
TimeLimiter timeLimiter = TimeLimiter.of(TimeLimiterConfig.ofDefaults());
metricRegistry.registerAll(TimeLimiterMetrics.ofTimeLimiter(timeLimiter));
Supplier<CompletableFuture<String>> futureSupplier = () ->
CompletableFuture.completedFuture("Hello world");

Try.ofCallable(timeLimiter.decorateFutureSupplier(futureSupplier));
Try.ofCallable(timeLimiter.decorateFutureSupplier(futureSupplier));

assertThat(metricRegistry).hasMetricsSize(3);
assertThat(metricRegistry).counter(DEFAULT_PREFIX + SUCCESSFUL)
.hasValue(2L);
assertThat(metricRegistry).counter(DEFAULT_PREFIX + FAILED)
.hasValue(0L);
assertThat(metricRegistry).counter(DEFAULT_PREFIX + TIMEOUT)
.hasValue(0L);
}

@Test
public void shouldRecordErrors() {
TimeLimiter timeLimiter = TimeLimiter.of(TimeLimiterConfig.ofDefaults());
metricRegistry.registerAll(TimeLimiterMetrics.ofTimeLimiter(timeLimiter));
Supplier<CompletableFuture<String>> futureSupplier = () ->
CompletableFuture.supplyAsync(this::fail);

Try.ofCallable(timeLimiter.decorateFutureSupplier(futureSupplier));
Try.ofCallable(timeLimiter.decorateFutureSupplier(futureSupplier));

assertThat(metricRegistry).hasMetricsSize(3);
assertThat(metricRegistry).counter(DEFAULT_PREFIX + SUCCESSFUL)
.hasValue(0L);
assertThat(metricRegistry).counter(DEFAULT_PREFIX + FAILED)
.hasValue(2L);
assertThat(metricRegistry).counter(DEFAULT_PREFIX + TIMEOUT)
.hasValue(0L);
}

@Test
public void shouldRecordTimeouts() {
TimeLimiter timeLimiter = TimeLimiter.of(TimeLimiterConfig.custom()
.timeoutDuration(Duration.ZERO)
.build());
metricRegistry.registerAll(TimeLimiterMetrics.ofTimeLimiter(timeLimiter));
Supplier<CompletableFuture<String>> futureSupplier = () ->
CompletableFuture.supplyAsync(this::fail);

Try.ofCallable(timeLimiter.decorateFutureSupplier(futureSupplier));
Try.ofCallable(timeLimiter.decorateFutureSupplier(futureSupplier));

assertThat(metricRegistry).hasMetricsSize(3);
assertThat(metricRegistry).counter(DEFAULT_PREFIX + SUCCESSFUL)
.hasValue(0L);
assertThat(metricRegistry).counter(DEFAULT_PREFIX + FAILED)
.hasValue(0L);
assertThat(metricRegistry).counter(DEFAULT_PREFIX + TIMEOUT)
.hasValue(2L);
}

private String fail() {
throw new RuntimeException();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.github.resilience4j.metrics.assertion;

import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Assertions;

import com.codahale.metrics.Counter;

public class CounterAssert extends AbstractAssert<CounterAssert, Counter> {

public CounterAssert(Counter actual) {
super(actual, CounterAssert.class);
}

public static CounterAssert assertThat(Counter actual) {
return new CounterAssert(actual);
}

public <T> CounterAssert hasValue(T expected) {
isNotNull();
Assertions.assertThat(actual.getCount())
.isEqualTo(expected);
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.github.resilience4j.metrics.assertion;

import org.assertj.core.api.AbstractAssert;
import org.assertj.core.api.Assertions;

import com.codahale.metrics.MetricRegistry;

public class MetricRegistryAssert extends AbstractAssert<MetricRegistryAssert, MetricRegistry> {

public MetricRegistryAssert(MetricRegistry actual) {
super(actual, MetricRegistryAssert.class);
}

public static MetricRegistryAssert assertThat(MetricRegistry actual) {
return new MetricRegistryAssert(actual);
}

public MetricRegistryAssert hasMetricsSize(int size) {
isNotNull();
Assertions.assertThat(actual.getMetrics())
.hasSize(size);
return this;
}

public CounterAssert counter(String name) {
isNotNull();
Assertions.assertThat(actual.getCounters()).containsKey(name);
return CounterAssert.assertThat(actual.getCounters().get(name));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ static <T, F extends Future<T>> Callable<T> decorateFutureSupplier(TimeLimiter t
return timeLimiter.decorateFutureSupplier(futureSupplier);
}

String getName();

/**
* Get the TimeLimiterConfig of this TimeLimiter decorator.
*
Expand Down Expand Up @@ -105,6 +107,11 @@ default <T, F extends Future<T>> T executeFutureSupplier(Supplier<F> futureSuppl
*/
<T, F extends Future<T>> Callable<T> decorateFutureSupplier(Supplier<F> futureSupplier);

/**
* Returns an EventPublisher which can be used to register event consumers.
*
* @return an EventPublisher
*/
EventPublisher getEventPublisher();

/**
Expand Down
Loading

0 comments on commit a56eb47

Please sign in to comment.