Skip to content
Open
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
3 changes: 3 additions & 0 deletions .fossa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ targets:
- type: gradle
path: ./
target: ':instrumentation:external-annotations:javaagent'
- type: gradle
path: ./
target: ':instrumentation:failsafe-3.0:javaagent'
- type: gradle
path: ./
target: ':instrumentation:failsafe-3.0:library'
Expand Down
19 changes: 19 additions & 0 deletions instrumentation/failsafe-3.0/javaagent/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
plugins {
id("otel.javaagent-instrumentation")
}

muzzle {
pass {
group.set("dev.failsafe")
module.set("failsafe")
versions.set("[3.0.1,)")
}
}

dependencies {
library("dev.failsafe:failsafe:3.0.1")

implementation(project(":instrumentation:failsafe-3.0:library"))

testImplementation(project(":instrumentation:failsafe-3.0:testing"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.failsafe.v3_0;

import static java.util.Collections.singletonList;

import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.internal.ExperimentalInstrumentationModule;
import java.util.List;

@AutoService(InstrumentationModule.class)
public class FailsafeInstrumentationModule extends InstrumentationModule
implements ExperimentalInstrumentationModule {

public FailsafeInstrumentationModule() {
super("failsafe", "failsafe-3.0");
}

@Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new RetryPolicyInstrumentation());
}

@Override
public boolean isIndyReady() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.failsafe.v3_0;

import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesNoArguments;

import dev.failsafe.PolicyConfig;
import dev.failsafe.internal.RetryPolicyImpl;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.instrumentation.failsafe.v3_0.FailsafeTelemetry;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.lang.reflect.Field;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

final class RetryPolicyInstrumentation implements TypeInstrumentation {
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
return named("dev.failsafe.RetryPolicyBuilder");
}

@Override
public void transform(TypeTransformer transformer) {
transformer.applyAdviceToMethod(
named("build").and(takesNoArguments()), this.getClass().getName() + "$BuildAdvice");
}

public static final class BuildAdvice {
@Advice.OnMethodExit
public static void onExit(@Advice.Return Object retryPolicyImpl)
throws NoSuchFieldException, IllegalAccessException {
RetryPolicyImpl<?> impl = (RetryPolicyImpl<?>) retryPolicyImpl;
FailsafeTelemetry failsafeTelemetry = FailsafeTelemetry.create(GlobalOpenTelemetry.get());

Field failureListenerField = PolicyConfig.class.getDeclaredField("failureListener");
failureListenerField.setAccessible(true);
failureListenerField.set(
impl.getConfig(),
failsafeTelemetry.createInstrumentedFailureListener(impl.getConfig(), impl.toString()));

Field successListenerField = PolicyConfig.class.getDeclaredField("successListener");
successListenerField.setAccessible(true);
successListenerField.set(
impl.getConfig(),
failsafeTelemetry.createInstrumentedSuccessListener(impl.getConfig(), impl.toString()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.instrumentation.failsafe.v3_0;

import dev.failsafe.CircuitBreaker;
import dev.failsafe.RetryPolicy;
import io.opentelemetry.instrumentation.failsafe.AbstractFailsafeInstrumentationTest;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

class FailsafeInstrumentationTest extends AbstractFailsafeInstrumentationTest {
@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();

@Override
protected InstrumentationExtension testing() {
return testing;
}

@Override
protected CircuitBreaker<Object> configure(CircuitBreaker<Object> userCircuitBreaker) {
return userCircuitBreaker;
}

@Override
protected RetryPolicy<Object> configure(RetryPolicy<Object> userRetryPolicy) {
return userRetryPolicy;
}

@Override
@Disabled
public void captureCircuitBreakerMetrics() {
// TODO: Will be enabled once Java agent CircuitBreaker instrumentation is added.
}

@Test
void captureRetryPolicyMetrics() {
captureRetryPolicyMetrics(null);
}
}
2 changes: 2 additions & 0 deletions instrumentation/failsafe-3.0/library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ plugins {

dependencies {
library("dev.failsafe:failsafe:3.0.1")

testImplementation(project(":instrumentation:failsafe-3.0:testing"))
}

tasks.test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
import dev.failsafe.CircuitBreakerConfig;
import dev.failsafe.RetryPolicy;
import dev.failsafe.RetryPolicyConfig;
import dev.failsafe.event.EventListener;
import dev.failsafe.event.ExecutionCompletedEvent;
import io.opentelemetry.api.OpenTelemetry;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
Expand Down Expand Up @@ -87,23 +89,8 @@ public <R> CircuitBreaker<R> createCircuitBreaker(
*/
public <R> RetryPolicy<R> createRetryPolicy(RetryPolicy<R> delegate, String retryPolicyName) {
RetryPolicyConfig<R> userConfig = delegate.getConfig();
Meter meter = openTelemetry.getMeter(INSTRUMENTATION_NAME);
LongCounter executionCounter =
meter
.counterBuilder("failsafe.retry_policy.execution.count")
.setDescription(
"Count of execution attempts processed by the retry policy, "
+ "where one execution represents the total number of attempts.")
.setUnit("{execution}")
.build();
LongHistogram attemptsHistogram =
meter
.histogramBuilder("failsafe.retry_policy.attempts")
.setDescription("Number of attempts for each execution.")
.setUnit("{attempt}")
.ofLongs()
.setExplicitBucketBoundariesAdvice(Arrays.asList(1L, 2L, 3L, 5L))
.build();
LongCounter executionCounter = buildRetryPolicyExecutionCounter();
LongHistogram attemptsHistogram = buildRetryPolicyAttemptsHistogram();
Attributes attributes = Attributes.of(RETRY_POLICY_NAME, retryPolicyName);
return RetryPolicy.builder(userConfig)
.onFailure(
Expand All @@ -114,4 +101,60 @@ public <R> RetryPolicy<R> createRetryPolicy(RetryPolicy<R> delegate, String retr
userConfig, executionCounter, attemptsHistogram, attributes))
.build();
}

/**
* Returns an instrumented failure listener.
*
* @param delegate user policy configuration
* @param retryPolicyName identifier for the policy being built
* @param <R> {@link RetryPolicyConfig}'s result type
* @return instrumented failure listener
*/
public <R> EventListener<ExecutionCompletedEvent<R>> createInstrumentedFailureListener(
RetryPolicyConfig<R> delegate, String retryPolicyName) {
LongCounter executionCounter = buildRetryPolicyExecutionCounter();
LongHistogram attemptsHistogram = buildRetryPolicyAttemptsHistogram();
Attributes attributes = Attributes.of(RETRY_POLICY_NAME, retryPolicyName);
return RetryPolicyEventListenerBuilders.buildInstrumentedFailureListener(
delegate, executionCounter, attemptsHistogram, attributes);
}

/**
* Returns an instrumented success listener.
*
* @param delegate user policy configuration
* @param retryPolicyName identifier for the policy being built
* @param <R> {@link RetryPolicyConfig}'s result type
* @return instrumented success listener
*/
public <R> EventListener<ExecutionCompletedEvent<R>> createInstrumentedSuccessListener(
RetryPolicyConfig<R> delegate, String retryPolicyName) {
LongCounter executionCounter = buildRetryPolicyExecutionCounter();
LongHistogram attemptsHistogram = buildRetryPolicyAttemptsHistogram();
Attributes attributes = Attributes.of(RETRY_POLICY_NAME, retryPolicyName);
return RetryPolicyEventListenerBuilders.buildInstrumentedSuccessListener(
delegate, executionCounter, attemptsHistogram, attributes);
}

private LongCounter buildRetryPolicyExecutionCounter() {
return openTelemetry
.getMeter(INSTRUMENTATION_NAME)
.counterBuilder("failsafe.retry_policy.execution.count")
.setDescription(
"Count of execution attempts processed by the retry policy, "
+ "where one execution represents the total number of attempts.")
.setUnit("{execution}")
.build();
}

private LongHistogram buildRetryPolicyAttemptsHistogram() {
return openTelemetry
.getMeter(INSTRUMENTATION_NAME)
.histogramBuilder("failsafe.retry_policy.attempts")
.setDescription("Number of attempts for each execution.")
.setUnit("{attempt}")
.ofLongs()
.setExplicitBucketBoundariesAdvice(Arrays.asList(1L, 2L, 3L, 5L))
.build();
}
}
Loading
Loading