Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

integrate metrics into the programmatic API #985

Merged
merged 2 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ default Runnable adaptRunnable(Runnable action) {
interface Builder<T, R> {
/**
* Assigns a description to the resulting set of configured fault tolerance strategies. The description
* is used in logging messages and exception messages.
* is used in logging messages and exception messages, and also as an identifier for metrics .
* <p>
* The description may be an arbitrary string. Duplicates are permitted.
* <p>
Expand Down
17 changes: 11 additions & 6 deletions doc/modules/ROOT/pages/integration/programmatic-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@ This page describes integration concerns of the xref:reference/programmatic-api.

== CDI Implementation

The CDI implementation will use the thread pool an integrator provides (see xref:integration/thread-pool.adoc[Thread Pool]).
This also extends to the context propagation integration (see xref:integration/context-propagation.adoc[Context Propagation]).

It will also use the event loop support, if integrator provides one (see xref:integration/event-loop.adoc[Event Loop]).

In other words, runtimes that use the CDI implementation don't have to do any extra integration work.
Runtimes that provide the CDI implementation of the programmatic API don't have to do any extra integration work.
The existing integration is enough.

== Standalone Implementation
Expand All @@ -20,11 +15,13 @@ It accepts a custom implementation of the `Configuration` interface, which allow

* `enabled()`: if `false`, all fault tolerance strategies except fallback and thread offload are disabled
* `executor()`: executor for thread offloads and other asynchronous tasks
* `metricsAdapter()`: adapter for metrics, see below

When no `Configuration` is provided, sensible defaults are used:

* fault tolerance is enabled unless system property `MP_Fault_Tolerance_NonFallback_Enabled` is set to `false`
* a thread pool obtained using `Executors.newCachedThreadPool()` is used as an executor
* no metrics are emitted

Users of the standalone implementation that also use an event loop based library, such as Vert.x, may integrate the event loop support as described in xref:integration/event-loop.adoc[Event Loop].

Expand All @@ -39,3 +36,11 @@ If `Configuration` was provided, the executor shutdown is left to the integrator
At the end of `StandaloneFaultTolerance.shutdown()`, the `Configuration.onShutdown()` method is called.

After `StandaloneFaultTolerance.shutdown()`, it is not possible to reinitialize {smallrye-fault-tolerance} again.

=== Metrics

In the standalone implementation, MicroProfile Metrics make no sense, as that is exclusively based on CDI.
It is however possible to integrate with Micrometer.

The `Configuration.metricsAdapter()` method must be implemented and return an instance of `io.smallrye.faulttolerance.standalone.MicrometerAdapter`.
The constructor of `MicrometerAdapter` accepts the Micrometer registry (`MeterRegistry`) to which metrics shall be emitted.
5 changes: 3 additions & 2 deletions doc/modules/ROOT/pages/reference/metrics.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ Micrometer recommends that libraries should not configure them out of the box, s

The following implementation makes sure Micrometer emits the same quantiles as MicroProfile Metrics for all fault tolerance metrics:

```java
[source,java]
----
static final MeterFilter ENABLE_HISTOGRAMS = new MeterFilter() {
@Override
public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
Expand All @@ -137,7 +138,7 @@ static final MeterFilter ENABLE_HISTOGRAMS = new MeterFilter() {
return config;
}
};
```
----

== Disabling Metrics

Expand Down
21 changes: 19 additions & 2 deletions doc/modules/ROOT/pages/reference/programmatic-api.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,25 @@ This may change in the future, though possibly only in the CDI implementation.

== Metrics

At the moment, the programmatic API of {smallrye-fault-tolerance} is not integrated with metrics.
This will change in the future, though possibly only in the CDI implementation.
The programmatic API is integrated with metrics.
All metrics, as described xref:reference/metrics.adoc[in the Metrics reference guide] and the linked guides, are supported.
The only difference is the value of the `method` tag.
With the programmatic API, the `method` tag will be set to the _description_ of the guarded operation, provided on the `FaultTolerance` builder.

[source,java]
----
private static final FaultTolerance<String> guarded = FaultTolerance.<String>create()
.withDescription("hello") // <1>
.withFallback().handler(() -> "fallback").done()
.build();
----

<1> A description of `hello` is set, it will be used as a value of the `method` tag in all metrics.

It is possible to create multiple `FaultTolerance` objects with the same description.
In this case, it won't be possbile to distinguish the different `FaultTolerance` objects in metrics; their values will be aggregated.

If no description is provided, a random UUID is used.

== Integration Concerns

Expand Down
13 changes: 12 additions & 1 deletion implementation/core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

<properties>
<!--suppress CheckTagEmptyBody -->
<jacoco.argLine /> <!-- will be set by the JaCoCo Maven plugin when active -->
<jacoco.argLine></jacoco.argLine> <!-- will be set by the JaCoCo Maven plugin when active -->
</properties>

<dependencies>
Expand All @@ -54,6 +54,17 @@
<artifactId>jboss-logging-processor</artifactId>
</dependency>

<dependency>
<groupId>org.eclipse.microprofile.metrics</groupId>
<artifactId>microprofile-metrics-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package io.smallrye.faulttolerance.core.apiimpl;

import io.smallrye.faulttolerance.core.metrics.MeteredOperation;

final class BasicMeteredOperationImpl implements MeteredOperation {
private final String name;
private final boolean isAsynchronous;
private final boolean hasBulkhead;
private final boolean hasCircuitBreaker;
private final boolean hasFallback;
private final boolean hasRateLimit;
private final boolean hasRetry;
private final boolean hasTimeout;

BasicMeteredOperationImpl(String name, boolean isAsynchronous, boolean hasBulkhead, boolean hasCircuitBreaker,
boolean hasFallback, boolean hasRateLimit, boolean hasRetry, boolean hasTimeout) {
this.name = name;
this.isAsynchronous = isAsynchronous;
this.hasBulkhead = hasBulkhead;
this.hasCircuitBreaker = hasCircuitBreaker;
this.hasFallback = hasFallback;
this.hasRateLimit = hasRateLimit;
this.hasRetry = hasRetry;
this.hasTimeout = hasTimeout;
}

@Override
public boolean isAsynchronous() {
return isAsynchronous;
}

@Override
public boolean hasBulkhead() {
return hasBulkhead;
}

@Override
public boolean hasCircuitBreaker() {
return hasCircuitBreaker;
}

@Override
public boolean hasFallback() {
return hasFallback;
}

@Override
public boolean hasRateLimit() {
return hasRateLimit;
}

@Override
public boolean hasRetry() {
return hasRetry;
}

@Override
public boolean hasTimeout() {
return hasTimeout;
}

@Override
public String name() {
return name;
}

@Override
public Object cacheKey() {
return name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.concurrent.ExecutorService;

import io.smallrye.faulttolerance.core.event.loop.EventLoop;
import io.smallrye.faulttolerance.core.metrics.MetricsProvider;
import io.smallrye.faulttolerance.core.timer.Timer;

// dependencies that must NOT be accessed eagerly; these are NOT safe to use during static initialization
Expand All @@ -14,4 +15,6 @@ public interface BuilderLazyDependencies {
EventLoop eventLoop();

Timer timer();

MetricsProvider metricsProvider();
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@
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.MeteredOperation;
import io.smallrye.faulttolerance.core.metrics.MetricsCollector;
import io.smallrye.faulttolerance.core.rate.limit.CompletionStageRateLimit;
import io.smallrye.faulttolerance.core.rate.limit.RateLimit;
import io.smallrye.faulttolerance.core.retry.BackOff;
Expand Down Expand Up @@ -291,6 +294,12 @@ private FaultToleranceStrategy<T> buildSyncStrategy(BuilderLazyDependencies lazy
fallbackBuilder.whenPredicate));
}

if (lazyDependencies.metricsProvider().isEnabled()) {
MeteredOperation meteredOperation = buildMeteredOperation();
result = new MetricsCollector<>(result, lazyDependencies.metricsProvider().create(meteredOperation),
meteredOperation);
}

return result;
}

Expand Down Expand Up @@ -362,6 +371,12 @@ private <V> FaultToleranceStrategy<CompletionStage<V>> buildAsyncStrategy(Builde
fallbackBuilder.whenPredicate));
}

if (lazyDependencies.metricsProvider().isEnabled()) {
MeteredOperation meteredOperation = buildMeteredOperation();
result = new CompletionStageMetricsCollector<>(result,
lazyDependencies.metricsProvider().create(meteredOperation), meteredOperation);
}

// thread offload is always enabled
if (!offloadToAnotherThread) {
result = new RememberEventLoop<>(result, lazyDependencies.eventLoop());
Expand All @@ -370,6 +385,12 @@ private <V> FaultToleranceStrategy<CompletionStage<V>> buildAsyncStrategy(Builde
return result;
}

private MeteredOperation buildMeteredOperation() {
return new BasicMeteredOperationImpl(description, isAsync, bulkheadBuilder != null,
circuitBreakerBuilder != null, fallbackBuilder != null, rateLimitBuilder != null,
retryBuilder != null, timeoutBuilder != null);
}

private static ExceptionDecision createExceptionDecision(Collection<Class<? extends Throwable>> consideredExpected,
Collection<Class<? extends Throwable>> consideredFailure, Predicate<Throwable> whenPredicate) {
if (whenPredicate != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@

public class CompletionStageMetricsCollector<V> extends MetricsCollector<CompletionStage<V>> {
public CompletionStageMetricsCollector(FaultToleranceStrategy<CompletionStage<V>> delegate, MetricsRecorder metrics,
boolean hasBulkhead, boolean hasCircuitBreaker, boolean hasRateLimit, boolean hasRetry, boolean hasTimeout) {
super(delegate, metrics, true, hasBulkhead, hasCircuitBreaker, hasRateLimit, hasRetry, hasTimeout);
MeteredOperation operation) {
super(delegate, metrics, operation);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.smallrye.faulttolerance.core.metrics;

public interface MeteredOperation {
boolean isAsynchronous();

boolean hasBulkhead();

boolean hasCircuitBreaker();

boolean hasFallback();

boolean hasRateLimit();

boolean hasRetry();

boolean hasTimeout();

String name();

Object cacheKey();
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,15 @@ public class MetricsCollector<V> implements FaultToleranceStrategy<V> {
private final AtomicLong runningExecutions = new AtomicLong();
private final AtomicLong waitingExecutions = new AtomicLong();

public MetricsCollector(FaultToleranceStrategy<V> delegate, MetricsRecorder metrics, boolean isAsync,
boolean hasBulkhead, boolean hasCircuitBreaker, boolean hasRateLimit, boolean hasRetry, boolean hasTimeout) {
public MetricsCollector(FaultToleranceStrategy<V> delegate, MetricsRecorder metrics, MeteredOperation operation) {
this.delegate = delegate;
this.metrics = metrics;
this.isAsync = isAsync;
this.hasBulkhead = hasBulkhead;
this.hasCircuitBreaker = hasCircuitBreaker;
this.hasRateLimit = hasRateLimit;
this.hasRetry = hasRetry;
this.hasTimeout = hasTimeout;
this.isAsync = operation.isAsynchronous();
this.hasBulkhead = operation.hasBulkhead();
this.hasCircuitBreaker = operation.hasCircuitBreaker();
this.hasRateLimit = operation.hasRateLimit();
this.hasRetry = operation.hasRetry();
this.hasTimeout = operation.hasTimeout();

this.state = CircuitBreakerState.CLOSED;
this.closedStart = System.nanoTime();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.smallrye.faulttolerance.metrics;
package io.smallrye.faulttolerance.core.metrics;

final class MetricConstants {
private MetricConstants() {
final class MetricsConstants {
private MetricsConstants() {
// avoid instantiation
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.smallrye.faulttolerance.core.metrics;

public interface MetricsProvider {
boolean isEnabled();

MetricsRecorder create(MeteredOperation operation);
}
Loading
Loading