diff --git a/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java index 2d0dfba6..1740d1e3 100644 --- a/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java +++ b/api/src/main/java/io/smallrye/faulttolerance/api/ApplyFaultTolerance.java @@ -13,7 +13,7 @@ /** * A special interceptor binding annotation to apply preconfigured fault tolerance. - * If {@code @ApplyFaultTolerance("<identifier>")} is present on a business method, + * If {@code @ApplyFaultTolerance("")} is present on a business method, * then a bean of type {@link FaultTolerance} with qualifier * {@link io.smallrye.common.annotation.Identifier @Identifier("<identifier>")} * must exist. Such bean serves as a preconfigured set of fault tolerance strategies @@ -42,8 +42,8 @@ *

* A single preconfigured fault tolerance can even be applied to multiple methods with different * return types, as long as the constraint on method asynchrony described above is obeyed. In such - * case, it is customary to declare the fault tolerance instance as {@code FaultTolerance<Object>} - * for synchronous methods, {@code FaultTolerance<CompletionStage<Object>>} for asynchronous + * case, it is customary to declare the fault tolerance instance as {@code FaultTolerance} + * for synchronous methods, {@code FaultTolerance>} for asynchronous * methods that return {@code CompletionStage}, and so on. Note that this effectively precludes * defining a useful fallback, because fallback can only be defined when the value type is known. */ diff --git a/doc/modules/ROOT/pages/reference/reusable.adoc b/doc/modules/ROOT/pages/reference/reusable.adoc index 1c7e1ac5..86d21aa8 100644 --- a/doc/modules/ROOT/pages/reference/reusable.adoc +++ b/doc/modules/ROOT/pages/reference/reusable.adoc @@ -50,3 +50,13 @@ Likewise, it is possible to do this for xref:reference/asynchronous.adoc#async-t Note that you can't define a synchronous `FaultTolerance` object and apply it to any asynchronous method. Similarly, you can't define an asynchronous `FaultTolerance>` and apply it to a synchronous method or an asynchronous method with different asynchronous type. This limitation will be lifted in the future. + +== Metrics + +Methods annotated `@ApplyFaultTolerance` gather metrics similarly to methods annotated with {microprofile-fault-tolerance} annotations. +That is, each method gets its own metrics, with the `method` tag being `.`. + +At the same time, state is still shared. +All methods annotated `@ApplyFaultTolerance` share the same bulkhead, circuit breaker and/or rate limit. + +If the `FaultTolerance` object used for `@ApplyFaultTolerance` is also used xref:reference/programmatic-api.adoc[programmatically], that usage is coalesced in metrics under the description as the `method` tag. diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java index c861b288..f28d3a25 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/FaultToleranceImpl.java @@ -37,9 +37,11 @@ import io.smallrye.faulttolerance.core.invocation.AsyncSupportRegistry; import io.smallrye.faulttolerance.core.invocation.Invoker; import io.smallrye.faulttolerance.core.invocation.StrategyInvoker; -import io.smallrye.faulttolerance.core.metrics.CompletionStageMetricsCollector; +import io.smallrye.faulttolerance.core.metrics.DelegatingCompletionStageMetricsCollector; +import io.smallrye.faulttolerance.core.metrics.DelegatingMetricsCollector; import io.smallrye.faulttolerance.core.metrics.MeteredOperation; -import io.smallrye.faulttolerance.core.metrics.MetricsCollector; +import io.smallrye.faulttolerance.core.metrics.MeteredOperationName; +import io.smallrye.faulttolerance.core.metrics.MetricsProvider; import io.smallrye.faulttolerance.core.rate.limit.CompletionStageRateLimit; import io.smallrye.faulttolerance.core.rate.limit.RateLimit; import io.smallrye.faulttolerance.core.retry.BackOff; @@ -103,22 +105,32 @@ public final class FaultToleranceImpl implements FaultTolerance { this.hasFallback = hasFallback; } - @Override - public T call(Callable action) throws Exception { + T call(Callable action, MeteredOperationName meteredOperationName) throws Exception { if (asyncSupport == null) { InvocationContext ctx = new InvocationContext<>(action); + if (meteredOperationName != null) { + ctx.set(MeteredOperationName.class, meteredOperationName); + } eventHandlers.register(ctx); return ((FaultToleranceStrategy) strategy).apply(ctx); } Invoker invoker = new CallableInvoker<>(action); InvocationContext> ctx = new InvocationContext<>(() -> asyncSupport.toCompletionStage(invoker)); + if (meteredOperationName != null) { + ctx.set(MeteredOperationName.class, meteredOperationName); + } eventHandlers.register(ctx); Invoker> wrapper = new StrategyInvoker<>(null, (FaultToleranceStrategy>) strategy, ctx); return asyncSupport.fromCompletionStage(wrapper); } + @Override + public T call(Callable action) throws Exception { + return call(action, null); + } + @Override public void run(Runnable action) { try { @@ -259,7 +271,7 @@ private void eagerInitialization() { } } - private FaultTolerance build(BuilderLazyDependencies lazyDependencies) { + private FaultToleranceImpl build(BuilderLazyDependencies lazyDependencies) { Consumer cbMaintenanceEventHandler = null; if (circuitBreakerBuilder != null && circuitBreakerBuilder.name != null) { cbMaintenanceEventHandler = eagerDependencies.cbMaintenance() @@ -285,12 +297,13 @@ private FaultTolerance build(BuilderLazyDependencies lazyDependencies) { return isAsync ? buildAsync(lazyDependencies, eventHandlers) : buildSync(lazyDependencies, eventHandlers); } - private FaultTolerance buildSync(BuilderLazyDependencies lazyDependencies, EventHandlers eventHandlers) { + private FaultToleranceImpl buildSync(BuilderLazyDependencies lazyDependencies, EventHandlers eventHandlers) { FaultToleranceStrategy strategy = buildSyncStrategy(lazyDependencies); - return new FaultToleranceImpl<>(strategy, (AsyncSupport) null, eventHandlers, fallbackBuilder != null); + return new FaultToleranceImpl<>(strategy, null, eventHandlers, fallbackBuilder != null); } - private FaultTolerance buildAsync(BuilderLazyDependencies lazyDependencies, EventHandlers eventHandlers) { + private FaultToleranceImpl, T> buildAsync(BuilderLazyDependencies lazyDependencies, + EventHandlers eventHandlers) { FaultToleranceStrategy> strategy = buildAsyncStrategy(lazyDependencies); AsyncSupport asyncSupport = AsyncSupportRegistry.get(new Class[0], asyncType); return new FaultToleranceImpl<>(strategy, asyncSupport, eventHandlers, fallbackBuilder != null); @@ -354,10 +367,10 @@ private FaultToleranceStrategy buildSyncStrategy(BuilderLazyDependencies lazy fallbackBuilder.whenPredicate)); } - if (lazyDependencies.metricsProvider().isEnabled()) { - MeteredOperation meteredOperation = buildMeteredOperation(); - result = new MetricsCollector<>(result, lazyDependencies.metricsProvider().create(meteredOperation), - meteredOperation); + MetricsProvider metricsProvider = lazyDependencies.metricsProvider(); + if (metricsProvider.isEnabled()) { + MeteredOperation defaultOperation = buildMeteredOperation(); + result = new DelegatingMetricsCollector<>(result, metricsProvider, defaultOperation); } return result; @@ -436,10 +449,10 @@ private FaultToleranceStrategy> buildAsyncStrategy(Builde fallbackBuilder.whenPredicate)); } - if (lazyDependencies.metricsProvider().isEnabled()) { - MeteredOperation meteredOperation = buildMeteredOperation(); - result = new CompletionStageMetricsCollector<>(result, - lazyDependencies.metricsProvider().create(meteredOperation), meteredOperation); + MetricsProvider metricsProvider = lazyDependencies.metricsProvider(); + if (metricsProvider.isEnabled()) { + MeteredOperation defaultOperation = buildMeteredOperation(); + result = new DelegatingCompletionStageMetricsCollector<>(result, metricsProvider, defaultOperation); } // thread offload is always enabled diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyFaultTolerance.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyFaultTolerance.java index c65ca6f3..b770caef 100644 --- a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyFaultTolerance.java +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/apiimpl/LazyFaultTolerance.java @@ -5,16 +5,17 @@ import java.util.function.Supplier; import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.core.metrics.MeteredOperationName; public final class LazyFaultTolerance implements FaultTolerance { - private final Supplier> builder; + private final Supplier> builder; private final Class asyncType; private final ReentrantLock lock = new ReentrantLock(); - private volatile FaultTolerance instance; + private volatile FaultToleranceImpl instance; - LazyFaultTolerance(Supplier> builder, Class asyncType) { + LazyFaultTolerance(Supplier> builder, Class asyncType) { this.builder = builder; this.asyncType = asyncType; } @@ -23,6 +24,10 @@ public Class internalGetAsyncType() { return asyncType; } + public T call(Callable action, MeteredOperationName meteredOperationName) throws Exception { + return instance().call(action, meteredOperationName); + } + @Override public T call(Callable action) throws Exception { return instance().call(action); @@ -50,8 +55,8 @@ public FaultTolerance castAsync(Class asyncType) { return instance().castAsync(asyncType); } - private FaultTolerance instance() { - FaultTolerance instance = this.instance; + private FaultToleranceImpl instance() { + FaultToleranceImpl instance = this.instance; if (instance == null) { lock.lock(); try { diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingCompletionStageMetricsCollector.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingCompletionStageMetricsCollector.java new file mode 100644 index 00000000..53e175f6 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingCompletionStageMetricsCollector.java @@ -0,0 +1,34 @@ +package io.smallrye.faulttolerance.core.metrics; + +import java.util.concurrent.CompletionStage; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +import io.smallrye.faulttolerance.core.InvocationContext; + +public class DelegatingCompletionStageMetricsCollector implements FaultToleranceStrategy> { + private final FaultToleranceStrategy> delegate; + private final MetricsProvider provider; + private final MeteredOperation originalOperation; + + private final ConcurrentMap> cache = new ConcurrentHashMap<>(); + + public DelegatingCompletionStageMetricsCollector(FaultToleranceStrategy> delegate, + MetricsProvider provider, MeteredOperation originalOperation) { + this.delegate = delegate; + this.provider = provider; + this.originalOperation = originalOperation; + } + + @Override + public CompletionStage apply(InvocationContext> ctx) throws Exception { + MeteredOperationName name = ctx.get(MeteredOperationName.class); + MeteredOperation operation = name != null + ? new DelegatingMeteredOperation(originalOperation, name.get()) + : originalOperation; + CompletionStageMetricsCollector delegate = cache.computeIfAbsent(operation, + ignored -> new CompletionStageMetricsCollector<>(this.delegate, provider.create(operation), operation)); + return delegate.apply(ctx); + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMeteredOperation.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMeteredOperation.java new file mode 100644 index 00000000..779090cb --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMeteredOperation.java @@ -0,0 +1,56 @@ +package io.smallrye.faulttolerance.core.metrics; + +final class DelegatingMeteredOperation implements MeteredOperation { + private final MeteredOperation operation; + private final String name; + + DelegatingMeteredOperation(MeteredOperation operation, String name) { + this.operation = operation; + this.name = name; + } + + @Override + public boolean isAsynchronous() { + return operation.isAsynchronous(); + } + + @Override + public boolean hasBulkhead() { + return operation.hasBulkhead(); + } + + @Override + public boolean hasCircuitBreaker() { + return operation.hasCircuitBreaker(); + } + + @Override + public boolean hasFallback() { + return operation.hasFallback(); + } + + @Override + public boolean hasRateLimit() { + return operation.hasRateLimit(); + } + + @Override + public boolean hasRetry() { + return operation.hasRetry(); + } + + @Override + public boolean hasTimeout() { + return operation.hasTimeout(); + } + + @Override + public String name() { + return name; + } + + @Override + public Object cacheKey() { + return name; + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMetricsCollector.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMetricsCollector.java new file mode 100644 index 00000000..62dcab7e --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/DelegatingMetricsCollector.java @@ -0,0 +1,33 @@ +package io.smallrye.faulttolerance.core.metrics; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import io.smallrye.faulttolerance.core.FaultToleranceStrategy; +import io.smallrye.faulttolerance.core.InvocationContext; + +public class DelegatingMetricsCollector implements FaultToleranceStrategy { + private final FaultToleranceStrategy delegate; + private final MetricsProvider provider; + private final MeteredOperation originalOperation; + + private final ConcurrentMap> cache = new ConcurrentHashMap<>(); + + public DelegatingMetricsCollector(FaultToleranceStrategy delegate, MetricsProvider provider, + MeteredOperation originalOperation) { + this.delegate = delegate; + this.provider = provider; + this.originalOperation = originalOperation; + } + + @Override + public V apply(InvocationContext ctx) throws Exception { + MeteredOperationName name = ctx.get(MeteredOperationName.class); + MeteredOperation operation = name != null + ? new DelegatingMeteredOperation(originalOperation, name.get()) + : originalOperation; + MetricsCollector delegate = cache.computeIfAbsent(operation, + ignored -> new MetricsCollector<>(this.delegate, provider.create(operation), operation)); + return delegate.apply(ctx); + } +} diff --git a/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MeteredOperationName.java b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MeteredOperationName.java new file mode 100644 index 00000000..e26b2242 --- /dev/null +++ b/implementation/core/src/main/java/io/smallrye/faulttolerance/core/metrics/MeteredOperationName.java @@ -0,0 +1,13 @@ +package io.smallrye.faulttolerance.core.metrics; + +public final class MeteredOperationName { + private final String name; + + public MeteredOperationName(String name) { + this.name = name; + } + + public String get() { + return name; + } +} diff --git a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceInterceptor.java b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceInterceptor.java index 71928693..bfbd4311 100644 --- a/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceInterceptor.java +++ b/implementation/fault-tolerance/src/main/java/io/smallrye/faulttolerance/FaultToleranceInterceptor.java @@ -78,6 +78,7 @@ import io.smallrye.faulttolerance.core.invocation.StrategyInvoker; import io.smallrye.faulttolerance.core.metrics.CompletionStageMetricsCollector; import io.smallrye.faulttolerance.core.metrics.MeteredOperation; +import io.smallrye.faulttolerance.core.metrics.MeteredOperationName; import io.smallrye.faulttolerance.core.metrics.MetricsCollector; import io.smallrye.faulttolerance.core.metrics.MetricsProvider; import io.smallrye.faulttolerance.core.rate.limit.CompletionStageRateLimit; @@ -210,14 +211,16 @@ private Object preconfiguredFlow(FaultToleranceOperation operation, InvocationCo throw new FaultToleranceException("Configured fault tolerance '" + identifier + "' is not created by the FaultTolerance API, this is not supported"); } + LazyFaultTolerance lazyFaultTolerance = (LazyFaultTolerance) faultTolerance; - Class asyncType = ((LazyFaultTolerance) faultTolerance).internalGetAsyncType(); + Class asyncType = lazyFaultTolerance.internalGetAsyncType(); + MeteredOperationName meteredOperationName = new MeteredOperationName(operation.getName()); AsyncSupport forOperation = AsyncSupportRegistry.get(operation.getParameterTypes(), operation.getReturnType()); AsyncSupport fromConfigured = asyncType == null ? null : AsyncSupportRegistry.get(new Class[0], asyncType); if (forOperation == null && fromConfigured == null) { - return faultTolerance.call(interceptionContext::proceed); + return lazyFaultTolerance.call(interceptionContext::proceed, meteredOperationName); } else if (forOperation == null) { throw new FaultToleranceException("Configured fault tolerance '" + identifier + "' expects the operation to " + fromConfigured.mustDescription() @@ -231,7 +234,7 @@ private Object preconfiguredFlow(FaultToleranceOperation operation, InvocationCo + "' expects the operation to " + fromConfigured.mustDescription() + ", but it " + forOperation.doesDescription() + ": " + operation); } else { - return faultTolerance.call(interceptionContext::proceed); + return lazyFaultTolerance.call(interceptionContext::proceed, meteredOperationName); } } diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyFaultTolerance.java new file mode 100644 index 00000000..974f9a15 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyFaultTolerance.java @@ -0,0 +1,21 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.metrics; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +import java.util.concurrent.CompletionStage; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.FaultTolerance; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final FaultTolerance> FT = FaultTolerance. createAsync() + .withRetry().maxRetries(2).done() + .withFallback().handler(() -> completedFuture("fallback")).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyService.java new file mode 100644 index 00000000..bb9b5998 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/MyService.java @@ -0,0 +1,22 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.metrics; + +import static java.util.concurrent.CompletableFuture.failedFuture; + +import java.util.concurrent.CompletionStage; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; + +@ApplicationScoped +public class MyService { + @ApplyFaultTolerance("my-fault-tolerance") + public CompletionStage first() { + return failedFuture(new IllegalArgumentException()); + } + + @ApplyFaultTolerance("my-fault-tolerance") + public CompletionStage second() { + return failedFuture(new IllegalStateException()); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/ReuseAsyncCompletionStageMetricsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/ReuseAsyncCompletionStageMetricsTest.java new file mode 100644 index 00000000..919dcf8e --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/completionstage/metrics/ReuseAsyncCompletionStageMetricsTest.java @@ -0,0 +1,60 @@ +package io.smallrye.faulttolerance.reuse.async.completionstage.metrics; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.concurrent.ExecutionException; + +import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.Tag; +import org.eclipse.microprofile.metrics.annotation.RegistryType; +import org.jboss.weld.junit5.auto.AddBeanClasses; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class ReuseAsyncCompletionStageMetricsTest { + @Test + public void test(MyService service, @RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry metrics) + throws ExecutionException, InterruptedException { + assertThat(service.first().toCompletableFuture().get()).isEqualTo("fallback"); + assertThat(service.second().toCompletableFuture().get()).isEqualTo("fallback"); + assertThat(service.second().toCompletableFuture().get()).isEqualTo("fallback"); + assertThat(service.second().toCompletableFuture().get()).isEqualTo("fallback"); + + // first + + assertThat(metrics.counter("ft.invocations.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.completionstage.metrics.MyService.first"), + new Tag("result", "valueReturned"), + new Tag("fallback", "applied")) + .getCount()).isEqualTo(1); + + assertThat(metrics.counter("ft.retry.retries.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.completionstage.metrics.MyService.first")) + .getCount()).isEqualTo(2); + assertThat(metrics.counter("ft.retry.calls.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.completionstage.metrics.MyService.first"), + new Tag("retried", "true"), + new Tag("retryResult", "maxRetriesReached")) + .getCount()).isEqualTo(1); + + // second + + assertThat(metrics.counter("ft.invocations.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.completionstage.metrics.MyService.second"), + new Tag("result", "valueReturned"), + new Tag("fallback", "applied")) + .getCount()).isEqualTo(3); + + assertThat(metrics.counter("ft.retry.retries.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.completionstage.metrics.MyService.second")) + .getCount()).isEqualTo(6); + assertThat(metrics.counter("ft.retry.calls.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.completionstage.metrics.MyService.second"), + new Tag("retried", "true"), + new Tag("retryResult", "maxRetriesReached")) + .getCount()).isEqualTo(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyFaultTolerance.java new file mode 100644 index 00000000..da79627b --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyFaultTolerance.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.reuse.async.uni.metrics; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.FaultTolerance; +import io.smallrye.faulttolerance.mutiny.api.MutinyFaultTolerance; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final FaultTolerance> FT = MutinyFaultTolerance. create() + .withRetry().maxRetries(2).done() + .withFallback().handler(() -> Uni.createFrom().item("fallback")).done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyService.java new file mode 100644 index 00000000..d8ccaa7b --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/MyService.java @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.reuse.async.uni.metrics; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; +import io.smallrye.mutiny.Uni; + +@ApplicationScoped +public class MyService { + @ApplyFaultTolerance("my-fault-tolerance") + public Uni first() { + return Uni.createFrom().failure(new IllegalArgumentException()); + } + + @ApplyFaultTolerance("my-fault-tolerance") + public Uni second() { + return Uni.createFrom().failure(new IllegalStateException()); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/ReuseAsyncUniMetricsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/ReuseAsyncUniMetricsTest.java new file mode 100644 index 00000000..5a325dfa --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/async/uni/metrics/ReuseAsyncUniMetricsTest.java @@ -0,0 +1,57 @@ +package io.smallrye.faulttolerance.reuse.async.uni.metrics; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.Tag; +import org.eclipse.microprofile.metrics.annotation.RegistryType; +import org.jboss.weld.junit5.auto.AddBeanClasses; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class ReuseAsyncUniMetricsTest { + @Test + public void test(MyService service, @RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry metrics) { + assertThat(service.first().await().indefinitely()).isEqualTo("fallback"); + assertThat(service.second().await().indefinitely()).isEqualTo("fallback"); + assertThat(service.second().await().indefinitely()).isEqualTo("fallback"); + assertThat(service.second().await().indefinitely()).isEqualTo("fallback"); + + // first + + assertThat(metrics.counter("ft.invocations.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.uni.metrics.MyService.first"), + new Tag("result", "valueReturned"), + new Tag("fallback", "applied")) + .getCount()).isEqualTo(1); + + assertThat(metrics.counter("ft.retry.retries.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.uni.metrics.MyService.first")) + .getCount()).isEqualTo(2); + assertThat(metrics.counter("ft.retry.calls.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.uni.metrics.MyService.first"), + new Tag("retried", "true"), + new Tag("retryResult", "maxRetriesReached")) + .getCount()).isEqualTo(1); + + // second + + assertThat(metrics.counter("ft.invocations.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.uni.metrics.MyService.second"), + new Tag("result", "valueReturned"), + new Tag("fallback", "applied")) + .getCount()).isEqualTo(3); + + assertThat(metrics.counter("ft.retry.retries.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.uni.metrics.MyService.second")) + .getCount()).isEqualTo(6); + assertThat(metrics.counter("ft.retry.calls.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.async.uni.metrics.MyService.second"), + new Tag("retried", "true"), + new Tag("retryResult", "maxRetriesReached")) + .getCount()).isEqualTo(3); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyFaultTolerance.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyFaultTolerance.java new file mode 100644 index 00000000..2b8c70a4 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyFaultTolerance.java @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.reuse.sync.metrics; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import io.smallrye.common.annotation.Identifier; +import io.smallrye.faulttolerance.api.FaultTolerance; + +@ApplicationScoped +public class MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + public static final FaultTolerance FT = FaultTolerance. create() + .withRetry().maxRetries(2).done() + .withFallback().handler(() -> "fallback").done() + .build(); +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyService.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyService.java new file mode 100644 index 00000000..a960fe83 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/MyService.java @@ -0,0 +1,18 @@ +package io.smallrye.faulttolerance.reuse.sync.metrics; + +import jakarta.enterprise.context.ApplicationScoped; + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance; + +@ApplicationScoped +public class MyService { + @ApplyFaultTolerance("my-fault-tolerance") + public String first() { + throw new IllegalArgumentException(); + } + + @ApplyFaultTolerance("my-fault-tolerance") + public String second() { + throw new IllegalStateException(); + } +} diff --git a/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/ReuseSyncMetricsTest.java b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/ReuseSyncMetricsTest.java new file mode 100644 index 00000000..9c61d3d5 --- /dev/null +++ b/testsuite/basic/src/test/java/io/smallrye/faulttolerance/reuse/sync/metrics/ReuseSyncMetricsTest.java @@ -0,0 +1,57 @@ +package io.smallrye.faulttolerance.reuse.sync.metrics; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.eclipse.microprofile.metrics.MetricRegistry; +import org.eclipse.microprofile.metrics.Tag; +import org.eclipse.microprofile.metrics.annotation.RegistryType; +import org.jboss.weld.junit5.auto.AddBeanClasses; +import org.junit.jupiter.api.Test; + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest; + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance.class) +public class ReuseSyncMetricsTest { + @Test + public void test(MyService service, @RegistryType(type = MetricRegistry.Type.BASE) MetricRegistry metrics) { + assertThat(service.first()).isEqualTo("fallback"); + assertThat(service.second()).isEqualTo("fallback"); + assertThat(service.second()).isEqualTo("fallback"); + assertThat(service.second()).isEqualTo("fallback"); + + // first + + assertThat(metrics.counter("ft.invocations.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.sync.metrics.MyService.first"), + new Tag("result", "valueReturned"), + new Tag("fallback", "applied")) + .getCount()).isEqualTo(1); + + assertThat(metrics.counter("ft.retry.retries.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.sync.metrics.MyService.first")) + .getCount()).isEqualTo(2); + assertThat(metrics.counter("ft.retry.calls.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.sync.metrics.MyService.first"), + new Tag("retried", "true"), + new Tag("retryResult", "maxRetriesReached")) + .getCount()).isEqualTo(1); + + // second + + assertThat(metrics.counter("ft.invocations.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.sync.metrics.MyService.second"), + new Tag("result", "valueReturned"), + new Tag("fallback", "applied")) + .getCount()).isEqualTo(3); + + assertThat(metrics.counter("ft.retry.retries.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.sync.metrics.MyService.second")) + .getCount()).isEqualTo(6); + assertThat(metrics.counter("ft.retry.calls.total", + new Tag("method", "io.smallrye.faulttolerance.reuse.sync.metrics.MyService.second"), + new Tag("retried", "true"), + new Tag("retryResult", "maxRetriesReached")) + .getCount()).isEqualTo(3); + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyFaultTolerance.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyFaultTolerance.kt new file mode 100644 index 00000000..dd67af50 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyFaultTolerance.kt @@ -0,0 +1,17 @@ +package io.smallrye.faulttolerance.kotlin.reuse.metrics + +import io.smallrye.common.annotation.Identifier +import io.smallrye.faulttolerance.api.FaultTolerance +import java.util.function.Supplier +import jakarta.enterprise.context.ApplicationScoped +import jakarta.enterprise.inject.Produces + +@ApplicationScoped +object MyFaultTolerance { + @Produces + @Identifier("my-fault-tolerance") + val FT = FaultTolerance.create() + .withRetry().maxRetries(2).done() + .withFallback().handler(Supplier { "fallback" }).done() + .build() +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyService.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyService.kt new file mode 100644 index 00000000..3db3ff1e --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/MyService.kt @@ -0,0 +1,19 @@ +package io.smallrye.faulttolerance.kotlin.reuse.metrics + +import io.smallrye.faulttolerance.api.ApplyFaultTolerance +import java.lang.IllegalArgumentException +import java.util.concurrent.atomic.AtomicInteger +import jakarta.enterprise.context.ApplicationScoped + +@ApplicationScoped +open class MyService { + @ApplyFaultTolerance("my-fault-tolerance") + open fun first(): String { + throw IllegalArgumentException() + } + + @ApplyFaultTolerance("my-fault-tolerance") + open fun second(): String { + throw IllegalStateException() + } +} diff --git a/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/ReuseKotlinMetricsTest.kt b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/ReuseKotlinMetricsTest.kt new file mode 100644 index 00000000..51659a60 --- /dev/null +++ b/testsuite/basic/src/test/kotlin/io/smallrye/faulttolerance/kotlin/reuse/metrics/ReuseKotlinMetricsTest.kt @@ -0,0 +1,55 @@ +package io.smallrye.faulttolerance.kotlin.reuse.metrics + +import io.smallrye.faulttolerance.util.FaultToleranceBasicTest +import org.assertj.core.api.Assertions.assertThat +import org.eclipse.microprofile.metrics.MetricRegistry +import org.eclipse.microprofile.metrics.Tag +import org.eclipse.microprofile.metrics.annotation.RegistryType +import org.jboss.weld.junit5.auto.AddBeanClasses +import org.junit.jupiter.api.Test + +@FaultToleranceBasicTest +@AddBeanClasses(MyFaultTolerance::class) +class ReuseKotlinMetricsTest { + @Test + fun test(service: MyService, @RegistryType(type = MetricRegistry.Type.BASE) metrics: MetricRegistry) { + assertThat(service.first()).isEqualTo("fallback") + assertThat(service.second()).isEqualTo("fallback") + assertThat(service.second()).isEqualTo("fallback") + assertThat(service.second()).isEqualTo("fallback") + + // first + + assertThat(metrics.counter("ft.invocations.total", + Tag("method", "io.smallrye.faulttolerance.kotlin.reuse.metrics.MyService.first"), + Tag("result", "valueReturned"), + Tag("fallback", "applied") + ).count).isEqualTo(1); + + assertThat(metrics.counter("ft.retry.retries.total", + Tag("method", "io.smallrye.faulttolerance.kotlin.reuse.metrics.MyService.first")) + .count).isEqualTo(2); + assertThat(metrics.counter("ft.retry.calls.total", + Tag("method", "io.smallrye.faulttolerance.kotlin.reuse.metrics.MyService.first"), + Tag("retried", "true"), + Tag("retryResult", "maxRetriesReached") + ).count).isEqualTo(1); + + // second + + assertThat(metrics.counter("ft.invocations.total", + Tag("method", "io.smallrye.faulttolerance.kotlin.reuse.metrics.MyService.second"), + Tag("result", "valueReturned"), + Tag("fallback", "applied") + ).count).isEqualTo(3); + + assertThat(metrics.counter("ft.retry.retries.total", + Tag("method", "io.smallrye.faulttolerance.kotlin.reuse.metrics.MyService.second") + ).count).isEqualTo(6); + assertThat(metrics.counter("ft.retry.calls.total", + Tag("method", "io.smallrye.faulttolerance.kotlin.reuse.metrics.MyService.second"), + Tag("retried", "true"), + Tag("retryResult", "maxRetriesReached") + ).count).isEqualTo(3); + } +}