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

Add a new spec section to cover integration with opentelemetry metrics #623

Merged
Merged
Changes from 1 commit
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
Next Next commit
Create package for testing fault tolerence recording metrics with mpT…
…elemetry
benjamin-confino committed Apr 22, 2024

Verified

This commit was signed with the committer’s verified signature.
benjamin-confino Benjamin Confino
commit fab82673611ecb40353a5a065e99fc0781ad93a0
29 changes: 29 additions & 0 deletions tck/formatter.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
Copyright (c) 2009-2024 Contributors to the Eclipse Foundation
See the NOTICE file(s) distributed with this work for additional
information regarding copyright ownership.
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.
-->
<profiles version="1">
<profile kind="CodeFormatterProfile" name="MicroProfile" version="1">
<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="120"/>
<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_field_declaration" value="16"/>
<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="true"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" value="18"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" value="16"/>
</profile>
</profiles>
36 changes: 32 additions & 4 deletions tck/pom.xml
Original file line number Diff line number Diff line change
@@ -3,9 +3,7 @@
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
@@ -17,7 +15,8 @@
<modelVersion>4.0.0</modelVersion>

<parent>
<!-- This is just for now and will not work if the API has a separate release cycle than the rest. -->
<!-- This is just for now and will not work if the API has a separate release
cycle than the rest. -->
<groupId>org.eclipse.microprofile.fault-tolerance</groupId>
<artifactId>microprofile-fault-tolerance-parent</artifactId>
<version>4.1-SNAPSHOT</version>
@@ -46,6 +45,17 @@
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<configuration>
<configFile>${project.basedir}/formatter.xml</configFile>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
@@ -55,6 +65,12 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>1.36.0</version>
</dependency>

<dependency>
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
@@ -75,6 +91,18 @@
<scope>provided</scope>
</dependency>

<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-metrics</artifactId>
<version>1.36.0</version>
</dependency>

<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
<version>1.36.0</version>
</dependency>

<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
@@ -109,5 +137,5 @@
</dependency>
</dependencies>


</project>
Original file line number Diff line number Diff line change
@@ -39,7 +39,8 @@ public class Bulkhead1Retry0MethodSyncBean {

@Bulkhead(value = 1)
@Retry(retryOn = {
BulkheadException.class}, delay = 1, delayUnit = ChronoUnit.SECONDS, maxRetries = 0, maxDuration = 999999)
BulkheadException.class},
delay = 1, delayUnit = ChronoUnit.SECONDS, maxRetries = 0, maxDuration = 999999)
public void test(Barrier barrier) {
barrier.await();
}
Original file line number Diff line number Diff line change
@@ -42,7 +42,8 @@ public class Bulkhead55RapidRetry10MethodAsynchBean {

@Bulkhead(waitingTaskQueue = 5, value = 5)
@Asynchronous
@Retry(retryOn = BulkheadException.class, delay = 1, delayUnit = ChronoUnit.MICROS, jitter = 0, maxRetries = 10, maxDuration = 999999)
@Retry(retryOn = BulkheadException.class, delay = 1, delayUnit = ChronoUnit.MICROS, jitter = 0, maxRetries = 10,
maxDuration = 999999)
public Future<?> test(Barrier barrier) {
barrier.await();
return CompletableFuture.completedFuture(null);
Original file line number Diff line number Diff line change
@@ -59,7 +59,8 @@ public String serviceWithTimeout() {
*
* @return should always throw TimeoutException
*/
@CircuitBreaker(successThreshold = 2, requestVolumeThreshold = 2, failureRatio = 0.75, delay = 50000, failOn = BulkheadException.class)
@CircuitBreaker(successThreshold = 2, requestVolumeThreshold = 2, failureRatio = 0.75, delay = 50000,
failOn = BulkheadException.class)
@Timeout(500) // Adjusted by config
public String serviceWithTimeoutWithoutFailOn() {
try {
Original file line number Diff line number Diff line change
@@ -58,7 +58,8 @@ public void serviceRetryOn(RuntimeException e, AtomicInteger counter) {
}

@Retry(retryOn = {TestConfigExceptionA.class,
TestConfigExceptionB.class}, abortOn = RuntimeException.class, maxRetries = 1, delay = 0, jitter = 0)
TestConfigExceptionB.class},
abortOn = RuntimeException.class, maxRetries = 1, delay = 0, jitter = 0)
public void serviceAbortOn(RuntimeException e, AtomicInteger counter) {
counter.getAndIncrement();
throw e;
@@ -75,7 +76,8 @@ public void serviceAbortOn(RuntimeException e, AtomicInteger counter) {
* <p>
* Limited to 10 seconds or 1000 retries, but will stop as soon as a delay of &gt; 100ms is observed.
*/
@Retry(abortOn = TestConfigExceptionA.class, delay = 0, jitter = 0, maxRetries = 1000, maxDuration = 10, durationUnit = ChronoUnit.SECONDS)
@Retry(abortOn = TestConfigExceptionA.class, delay = 0, jitter = 0, maxRetries = 1000, maxDuration = 10,
durationUnit = ChronoUnit.SECONDS)
public void serviceJitter() {
long startTime = System.nanoTime();
if (lastStartTime != 0) {
Original file line number Diff line number Diff line change
@@ -40,6 +40,7 @@
import java.util.concurrent.ExecutionException;

import org.eclipse.microprofile.fault.tolerance.tck.config.ConfigAnnotationAsset;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.AllMetricsBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.RetryResult;
@@ -175,7 +176,7 @@ public void testMetricUnits() throws InterruptedException, ExecutionException {

/**
* Gets metric unit from metadata via reflection which works for Metrics 2.x and 3.x
*
*
* @param metadata
* the metadata
* @return the unit or {@code MetricUnits.NONE} if the metadata has no unit
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@
package org.eclipse.microprofile.fault.tolerance.tck.metrics;

import static java.util.concurrent.TimeUnit.MINUTES;
import static org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricComparator.approxMillis;
import static org.eclipse.microprofile.fault.tolerance.tck.metrics.common.util.TimeUtils.approxMillis;
import static org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.BulkheadResult.ACCEPTED;
import static org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.BulkheadResult.REJECTED;
import static org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.InvocationResult.EXCEPTION_THROWN;
@@ -35,6 +35,8 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;

import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.BulkheadMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.util.TimeUtils;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.AsyncCaller;
@@ -58,7 +60,7 @@ public class BulkheadMetricTest extends Arquillian {
public static WebArchive deploy() {

WebArchive war = ShrinkWrap.create(WebArchive.class, "ftMetricBulkhead.war")
.addClasses(BulkheadMetricBean.class)
.addClasses(BulkheadMetricBean.class, TimeUtils.class)
.addPackage(Packages.UTILS)
.addPackage(Packages.METRIC_UTILS)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml");
Original file line number Diff line number Diff line change
@@ -30,8 +30,9 @@
import static org.hamcrest.Matchers.is;

import org.eclipse.microprofile.fault.tolerance.tck.config.ConfigAnnotationAsset;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.CircuitBreakerMetricBean.Result;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.CircuitBreakerMetricBean.SkippedException;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.CircuitBreakerMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.CircuitBreakerMetricBean.Result;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.CircuitBreakerMetricBean.SkippedException;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@

import java.util.concurrent.ExecutionException;

import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.ClashingNameBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricGetter;
import org.jboss.arquillian.container.test.api.Deployment;
Original file line number Diff line number Diff line change
@@ -24,6 +24,7 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.ClassLevelMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.RetryResult;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.RetryRetried;
Original file line number Diff line number Diff line change
@@ -25,8 +25,10 @@
import static org.hamcrest.Matchers.is;
import static org.testng.Assert.expectThrows;

import org.eclipse.microprofile.fault.tolerance.tck.metrics.FallbackMetricBean.Action;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.FallbackMetricBean.NonFallbackException;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.FallbackMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.FallbackMetricBean.Action;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.FallbackMetricBean.NonFallbackException;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.FallbackMetricHandler;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@

import java.util.concurrent.ExecutionException;

import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.AllMetricsBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.RetryResult;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.RetryRetried;
Original file line number Diff line number Diff line change
@@ -28,8 +28,9 @@
import java.time.Duration;

import org.eclipse.microprofile.fault.tolerance.tck.config.ConfigAnnotationAsset;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.RetryMetricBean.CallCounter;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.RetryMetricBean.NonRetryableException;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.RetryMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.RetryMetricBean.CallCounter;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.RetryMetricBean.NonRetryableException;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.RetryResult;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.RetryRetried;
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@
import static org.hamcrest.Matchers.is;

import org.eclipse.microprofile.fault.tolerance.tck.config.ConfigAnnotationAsset;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.TimeoutMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricDefinition.TimeoutTimedOut;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricGetter;
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.eclipse.microprofile.fault.tolerance.tck.metrics;
package org.eclipse.microprofile.fault.tolerance.tck.metrics.common;

import java.time.temporal.ChronoUnit;
import java.util.concurrent.CompletableFuture;
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.eclipse.microprofile.fault.tolerance.tck.metrics;
package org.eclipse.microprofile.fault.tolerance.tck.metrics.common;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.eclipse.microprofile.fault.tolerance.tck.metrics;
package org.eclipse.microprofile.fault.tolerance.tck.metrics.common;

import org.eclipse.microprofile.fault.tolerance.tck.util.TestException;
import org.eclipse.microprofile.faulttolerance.CircuitBreaker;
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.eclipse.microprofile.fault.tolerance.tck.metrics;
package org.eclipse.microprofile.fault.tolerance.tck.metrics.common;

import java.time.temporal.ChronoUnit;
import java.util.concurrent.CompletableFuture;
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.eclipse.microprofile.fault.tolerance.tck.metrics;
package org.eclipse.microprofile.fault.tolerance.tck.metrics.common;

import org.eclipse.microprofile.fault.tolerance.tck.util.TestException;
import org.eclipse.microprofile.faulttolerance.Retry;
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.eclipse.microprofile.fault.tolerance.tck.metrics;
package org.eclipse.microprofile.fault.tolerance.tck.metrics.common;

import org.eclipse.microprofile.fault.tolerance.tck.util.TestException;
import org.eclipse.microprofile.faulttolerance.Fallback;
Original file line number Diff line number Diff line change
@@ -17,10 +17,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.eclipse.microprofile.fault.tolerance.tck.metrics;
package org.eclipse.microprofile.fault.tolerance.tck.metrics.common;

import org.eclipse.microprofile.fault.tolerance.tck.metrics.FallbackMetricBean.Action;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.FallbackMetricBean.NonFallbackException;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.FallbackMetricBean.Action;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.FallbackMetricBean.NonFallbackException;
import org.eclipse.microprofile.fault.tolerance.tck.util.TestException;
import org.eclipse.microprofile.faulttolerance.ExecutionContext;
import org.eclipse.microprofile.faulttolerance.FallbackHandler;
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.eclipse.microprofile.fault.tolerance.tck.metrics;
package org.eclipse.microprofile.fault.tolerance.tck.metrics.common;

import java.time.Duration;

Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.eclipse.microprofile.fault.tolerance.tck.metrics;
package org.eclipse.microprofile.fault.tolerance.tck.metrics.common;

import org.eclipse.microprofile.faulttolerance.Timeout;

Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*******************************************************************************/
package org.eclipse.microprofile.fault.tolerance.tck.metrics.util;
package org.eclipse.microprofile.fault.tolerance.tck.metrics.common.util;

import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.lessThan;
@@ -26,10 +26,10 @@
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;

public class MetricComparator {
public class TimeUtils {

// Utility class, no public constructor
private MetricComparator() {
private TimeUtils() {
}

/**
@@ -39,7 +39,7 @@ private MetricComparator() {
* conversion and creates a {@link Matcher} to do the check.
* <p>
* Useful for checking the results from Histograms.
*
*
* @param originalMillis
* the expected time in milliseconds
* @return a {@link Matcher} which matches against a time in nanoseconds
@@ -58,7 +58,7 @@ public static Matcher<Long> approxMillis(final long originalMillis) {
* and creates a {@link Matcher} to do the check.
* <p>
* Useful for checking the results from Histograms.
*
*
* @param originalMillis
* the expected time in milliseconds
* @return a {@link Matcher} which matches against a time in nanoseconds
Original file line number Diff line number Diff line change
@@ -50,26 +50,36 @@
public enum MetricDefinition {
INVOCATIONS("ft.invocations.total", Counter.class, InvocationResult.class, InvocationFallback.class), RETRY_CALLS(
"ft.retry.calls.total", Counter.class, RetryRetried.class,
RetryResult.class), RETRY_RETRIES("ft.retry.retries.total", Counter.class), TIMEOUT_CALLS(
"ft.timeout.calls.total", Counter.class,
TimeoutTimedOut.class), TIMEOUT_EXECUTION_DURATION("ft.timeout.executionDuration", Histogram.class,
MetricUnits.NANOSECONDS), CIRCUITBREAKER_CALLS("ft.circuitbreaker.calls.total",
Counter.class, CircuitBreakerResult.class), CIRCUITBREAKER_STATE(
"ft.circuitbreaker.state.total", Gauge.class, MetricUnits.NANOSECONDS,
CircuitBreakerState.class), CIRCUITBREAKER_OPENED(
"ft.circuitbreaker.opened.total", Counter.class), BULKHEAD_CALLS(
"ft.bulkhead.calls.total", Counter.class,
BulkheadResult.class), BULKHEAD_EXECUTIONS_RUNNING(
"ft.bulkhead.executionsRunning",
Gauge.class), BULKHEAD_EXECUTIONS_WAITING(
"ft.bulkhead.executionsWaiting",
Gauge.class), BULKHEAD_RUNNING_DURATION(
"ft.bulkhead.runningDuration",
Histogram.class,
MetricUnits.NANOSECONDS), BULKHEAD_WAITING_DURATION(
"ft.bulkhead.waitingDuration",
Histogram.class,
MetricUnits.NANOSECONDS);
RetryResult.class),
RETRY_RETRIES("ft.retry.retries.total", Counter.class), TIMEOUT_CALLS(
"ft.timeout.calls.total", Counter.class,
TimeoutTimedOut.class),
TIMEOUT_EXECUTION_DURATION("ft.timeout.executionDuration", Histogram.class,
MetricUnits.NANOSECONDS),
CIRCUITBREAKER_CALLS("ft.circuitbreaker.calls.total",
Counter.class, CircuitBreakerResult.class),
CIRCUITBREAKER_STATE(
"ft.circuitbreaker.state.total", Gauge.class, MetricUnits.NANOSECONDS,
CircuitBreakerState.class),
CIRCUITBREAKER_OPENED(
"ft.circuitbreaker.opened.total", Counter.class),
BULKHEAD_CALLS(
"ft.bulkhead.calls.total", Counter.class,
BulkheadResult.class),
BULKHEAD_EXECUTIONS_RUNNING(
"ft.bulkhead.executionsRunning",
Gauge.class),
BULKHEAD_EXECUTIONS_WAITING(
"ft.bulkhead.executionsWaiting",
Gauge.class),
BULKHEAD_RUNNING_DURATION(
"ft.bulkhead.runningDuration",
Histogram.class,
MetricUnits.NANOSECONDS),
BULKHEAD_WAITING_DURATION(
"ft.bulkhead.waitingDuration",
Histogram.class,
MetricUnits.NANOSECONDS);

private String name;
private String unit;
@@ -93,7 +103,7 @@ private MetricDefinition(String name, Class<? extends Metric> metricClass,

/**
* The metric name
*
*
* @return the name
*/
public String getName() {
@@ -102,7 +112,7 @@ public String getName() {

/**
* The metric unit
*
*
* @return the unit
*/
public String getUnit() {
@@ -111,7 +121,7 @@ public String getUnit() {

/**
* The subclass of {@link Metric} used by this metric
*
*
* @return the metric class
*/
public Class<? extends Metric> getMetricClass() {
@@ -122,7 +132,7 @@ public Class<? extends Metric> getMetricClass() {
* The tags which are applied to this metric
* <p>
* The classes returned from this method will be enums which implement {@link TagValue}
*
*
* @return the tags which are applied to this metric
*/
public Class<? extends TagValue>[] getTagClasses() {
@@ -205,7 +215,8 @@ public Tag getTag() {

public enum RetryResult implements TagValue {
VALUE_RETURNED("valueReturned"), EXCEPTION_NOT_RETRYABLE("exceptionNotRetryable"), MAX_RETRIES_REACHED(
"maxRetriesReached"), MAX_DURATION_REACHED("maxDurationReached");
"maxRetriesReached"),
MAX_DURATION_REACHED("maxDurationReached");

private Tag tag;

@@ -246,4 +257,4 @@ public Tag getTag() {
}
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright (c) 2018-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics;

import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.BulkheadResult.ACCEPTED;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.BulkheadResult.REJECTED;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerResult.CIRCUIT_BREAKER_OPEN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerResult.FAILURE;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerResult.SUCCESS;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerState.CLOSED;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerState.HALF_OPEN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerState.OPEN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.EXCEPTION_THROWN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.VALUE_RETURNED;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;

import java.util.Properties;
import java.util.concurrent.ExecutionException;

import org.eclipse.microprofile.fault.tolerance.tck.config.ConfigAnnotationAsset;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.AllMetricsBean;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.PullExporterAutoConfigurationCustomizerProvider;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.RetryResult;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.RetryRetried;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.TimeoutTimedOut;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.Test;

import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import jakarta.inject.Inject;

/**
* Test that metrics are created when all the Fault Tolerance annotations are placed on the same method
*/
public class AllAnnotationTelemetryTest extends Arquillian {

@Deployment
public static WebArchive deploy() {

Properties props = new Properties();
props.put("otel.sdk.disabled", "false");
props.put("otel.traces.exporter", "none");

// Scales the following method's annotation values by the TCKConfig baseMultiplier
ConfigAnnotationAsset allMetricsBeanConfig = new ConfigAnnotationAsset()
.autoscaleMethod(AllMetricsBean.class, "doWork")
.mergeProperties(props);

JavaArchive jar = ShrinkWrap.create(JavaArchive.class, "ftMetricAll.jar")
.addClass(AllMetricsBean.class)
.addPackage(Packages.UTILS)
.addPackage(Packages.TELEMETRY_METRIC_UTILS)
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsManifestResource(allMetricsBeanConfig, "microprofile-config.properties")
.addAsServiceProvider(AutoConfigurationCustomizerProvider.class,
PullExporterAutoConfigurationCustomizerProvider.class);

WebArchive war = ShrinkWrap.create(WebArchive.class, "ftMetricAll.war")
.addAsLibrary(jar);

return war;
}

@Inject
private AllMetricsBean allMetricsBean;

@Test
public void testAllMetrics() throws InterruptedException, ExecutionException {
TelemetryMetricGetter m = new TelemetryMetricGetter(AllMetricsBean.class, "doWork");
m.baselineMetrics();

allMetricsBean.doWork().get(); // Should succeed on first attempt

// General metrics
assertThat("successful without fallback",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_APPLIED).delta(), is(1L));
assertThat("successful with fallback", m.getInvocations(VALUE_RETURNED, InvocationFallback.APPLIED).delta(),
is(0L));
assertThat("failed without fallback",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_APPLIED).delta(), is(0L));
assertThat("failed with fallback", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.APPLIED).delta(),
is(0L));

// Retry metrics
assertThat("value returned, no retry", m.getRetryCalls(RetryRetried.FALSE, RetryResult.VALUE_RETURNED).delta(),
is(1L));
assertThat("exception thrown, no retry",
m.getRetryCalls(RetryRetried.FALSE, RetryResult.EXCEPTION_NOT_RETRYABLE).delta(), is(0L));
assertThat("max retries reached, no retry",
m.getRetryCalls(RetryRetried.FALSE, RetryResult.MAX_RETRIES_REACHED).delta(), is(0L));
assertThat("max duration reached, no retry",
m.getRetryCalls(RetryRetried.FALSE, RetryResult.MAX_DURATION_REACHED).delta(), is(0L));
assertThat("value returned after retry", m.getRetryCalls(RetryRetried.TRUE, RetryResult.VALUE_RETURNED).delta(),
is(0L));
assertThat("exception thrown after retry",
m.getRetryCalls(RetryRetried.TRUE, RetryResult.EXCEPTION_NOT_RETRYABLE).delta(), is(0L));
assertThat("max retries reached after retry",
m.getRetryCalls(RetryRetried.TRUE, RetryResult.MAX_RETRIES_REACHED).delta(), is(0L));
assertThat("max duration reached after retry",
m.getRetryCalls(RetryRetried.TRUE, RetryResult.MAX_DURATION_REACHED).delta(), is(0L));
assertThat("retries", m.getRetryRetries().delta(), is(0L));

// Timeout metrics
assertThat("timeout execution duration histogram present", m.getTimeoutExecutionDuration().isPresent(),
is(true));
assertThat("timed out calls", m.getTimeoutCalls(TimeoutTimedOut.TRUE).delta(), is(0L));
assertThat("non timed out calls", m.getTimeoutCalls(TimeoutTimedOut.FALSE).delta(), is(1L));

// CircuitBreaker metrics
assertThat("circuitbreaker succeeded calls", m.getCircuitBreakerCalls(SUCCESS).delta(), is(1L));
assertThat("circuitbreaker failed calls", m.getCircuitBreakerCalls(FAILURE).delta(), is(0L));
assertThat("circuitbreaker prevented calls", m.getCircuitBreakerCalls(CIRCUIT_BREAKER_OPEN).delta(), is(0L));
assertThat("circuitbreaker closed time", m.getCircuitBreakerState(CLOSED).delta(), greaterThan(0L));
assertThat("circuitbreaker half open time", m.getCircuitBreakerState(HALF_OPEN).delta(), is(0L));
assertThat("circuitbreaker open time", m.getCircuitBreakerState(OPEN).delta(), is(0L));
assertThat("circuitbreaker times opened", m.getCircuitBreakerOpened().delta(), is(0L));

// Bulkhead metrics
assertThat("bulkhead accepted calls", m.getBulkheadCalls(ACCEPTED).delta(), is(1L));
assertThat("bulkhead rejected calls", m.getBulkheadCalls(REJECTED).delta(), is(0L));
assertThat("bulkhead executions running present", m.getBulkheadExecutionsRunning().isPresent(),
is(true));
assertThat("bulkhead executions running value", m.getBulkheadExecutionsRunning().value(), is(0L));
assertThat("bulkhead running duration histogram present", m.getBulkheadRunningDuration().isPresent(),
is(true));
assertThat("bulkhead executions waiting present", m.getBulkheadExecutionsWaiting().isPresent(),
is(true));
assertThat("bulkhead executions waiting value", m.getBulkheadExecutionsWaiting().value(), is(0L));
assertThat("bulkhead queue wait time histogram present", m.getBulkheadRunningDuration().isPresent(),
is(true));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
/*
* Copyright (c) 2018-2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics;

import static java.util.concurrent.TimeUnit.MINUTES;
import static org.eclipse.microprofile.fault.tolerance.tck.metrics.common.util.TimeUtils.approxMillis;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.BulkheadResult.ACCEPTED;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.BulkheadResult.REJECTED;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.EXCEPTION_THROWN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.VALUE_RETURNED;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expectBulkheadException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;

import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.BulkheadMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.util.TimeUtils;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.InMemoryMetricReader;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.PullExporterAutoConfigurationCustomizerProvider;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.AsyncCaller;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
import org.eclipse.microprofile.fault.tolerance.tck.util.TCKConfig;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;

import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import io.opentelemetry.sdk.metrics.data.HistogramPointData;
import jakarta.inject.Inject;

public class BulkheadTelemetryTest extends Arquillian {

@Deployment
public static WebArchive deploy() {

WebArchive war = ShrinkWrap.create(WebArchive.class, "ftMetricBulkhead.war")
.addClasses(BulkheadMetricBean.class, TimeUtils.class)
.addPackage(Packages.UTILS)
.addPackage(Packages.TELEMETRY_METRIC_UTILS)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsResource(new StringAsset(
"otel.sdk.disabled=false\notel.traces.exporter=none"),
"META-INF/microprofile-config.properties")
.addAsServiceProvider(AutoConfigurationCustomizerProvider.class,
PullExporterAutoConfigurationCustomizerProvider.class);
return war;
}

@Inject
private BulkheadMetricBean bulkheadBean;
@Inject
private AsyncCaller async;

private TCKConfig config = TCKConfig.getConfig();

private List<CompletableFuture<Void>> waitingFutures = new ArrayList<>();

/**
* Ensure that any waiting futures get completed at the end of each test
* <p>
* Important in case tests end early due to an exception or failure.
*/
@AfterMethod
public void completeWaitingFutures() {
for (CompletableFuture<Void> future : waitingFutures) {
future.complete(null);
}
waitingFutures.clear();
}

/**
* Use this method to obtain futures for passing to methods on {@link BulkheadMetricBean}
* <p>
* Using this factory method ensures they will be completed at the end of the test if your test fails.
*/
private CompletableFuture<Void> newWaitingFuture() {
CompletableFuture<Void> result = new CompletableFuture<>();
waitingFutures.add(result);
return result;
}

@Test(groups = "main")
public void bulkheadMetricTest() throws InterruptedException, ExecutionException, TimeoutException {
System.out.println("GREP + bulkheadMetricTest start");
TelemetryMetricGetter m = new TelemetryMetricGetter(BulkheadMetricBean.class, "waitFor");
m.baselineMetrics();
System.out.println("GREP + bulkheadMetricTest after baseline");

CompletableFuture<Void> waitingFuture = newWaitingFuture();

Future<?> f1 = async.run(() -> bulkheadBean.waitFor(waitingFuture));
Future<?> f2 = async.run(() -> bulkheadBean.waitFor(waitingFuture));

System.out.println("GREP + bulkheadMetricTest before fail");
bulkheadBean.waitForRunningExecutions(2);
assertThat("executions running", m.getBulkheadExecutionsRunning().value(), is(2L));

waitingFuture.complete(null);
f1.get(1, MINUTES);
f2.get(1, MINUTES);

assertThat("executions running", m.getBulkheadExecutionsRunning().value(), is(0L));
assertThat("accepted calls", m.getBulkheadCalls(ACCEPTED).delta(), is(2L));
assertThat("rejected calls", m.getBulkheadCalls(REJECTED).delta(), is(0L));

// Async metrics should not be present
assertThat("bulkhead executions waiting present", m.getBulkheadExecutionsWaiting().isPresent(),
is(false));
assertThat("bulkhead waiting duration present", m.getBulkheadWaitingDuration().isPresent(), is(false));

// General metrics should be updated
assertThat("successful invocations", m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(),
is(2L));
assertThat("failed invocations", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(),
is(0L));
}

@Test(groups = "main")
public void bulkheadMetricRejectionTest() throws InterruptedException, ExecutionException, TimeoutException {
TelemetryMetricGetter m = new TelemetryMetricGetter(BulkheadMetricBean.class, "waitFor");
m.baselineMetrics();

CompletableFuture<Void> waitingFuture = newWaitingFuture();

Future<?> f1 = async.run(() -> bulkheadBean.waitFor(waitingFuture));
Future<?> f2 = async.run(() -> bulkheadBean.waitFor(waitingFuture));

bulkheadBean.waitForRunningExecutions(2);

Future<?> f3 = async.run(() -> bulkheadBean.waitFor(waitingFuture));
expectBulkheadException(f3);

assertThat("executions running", m.getBulkheadExecutionsRunning().value(), is(2L));

waitingFuture.complete(null);
f1.get(1, MINUTES);
f2.get(1, MINUTES);

assertThat("executions running", m.getBulkheadExecutionsRunning().value(), is(0L));
assertThat("accepted calls", m.getBulkheadCalls(ACCEPTED).delta(), is(2L));
assertThat("rejected calls", m.getBulkheadCalls(REJECTED).delta(), is(1L));

// General metrics should be updated
assertThat("successful invocations", m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(),
is(2L));
assertThat("failed invocations", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(),
is(1L));

}

@Test(groups = "main")
public void bulkheadMetricHistogramTest() throws InterruptedException, ExecutionException, TimeoutException {
TelemetryMetricGetter m = new TelemetryMetricGetter(BulkheadMetricBean.class, "waitForHistogram");
m.baselineMetrics();

CompletableFuture<Void> waitingFuture = newWaitingFuture();

Future<?> f1 = async.run(() -> bulkheadBean.waitForHistogram(waitingFuture));
Future<?> f2 = async.run(() -> bulkheadBean.waitForHistogram(waitingFuture));
bulkheadBean.waitForRunningExecutions(2);
Future<?> f3 = async.run(() -> bulkheadBean.waitForHistogram(waitingFuture));

expectBulkheadException(f3);

Thread.sleep(config.getTimeoutInMillis(1000));

waitingFuture.complete(null);
f1.get(1, MINUTES);
f2.get(1, MINUTES);

Long executionTimesCount = m.getBulkheadRunningDuration().getHistogramCount().get();
assertThat("histogram count", executionTimesCount, is(2L)); // Rejected executions
// not recorded in

Collection<HistogramPointData> executionTimesPoints = m.getBulkheadRunningDuration().getHistogramPoints();
double time = executionTimesPoints.stream()
.mapToDouble(points -> points.getSum())
.sum();

long count = executionTimesPoints.stream()
.mapToLong(points -> points.getCount())
.sum();

assertThat("mean", Math.round(time / count), approxMillis(1000)); // histogram

// Now let's put some quick results through the bulkhead
bulkheadBean.waitForHistogram(CompletableFuture.completedFuture(null));
bulkheadBean.waitForHistogram(CompletableFuture.completedFuture(null));

// Should have 4 results, ~0ms * 2 and ~1000ms * 2
executionTimesCount = m.getBulkheadRunningDuration().getHistogramCount().get();
assertThat("histogram count", executionTimesCount, is(4L));
}

@Test(groups = "main")
public void bulkheadMetricAsyncTest() throws InterruptedException, ExecutionException, TimeoutException {
TelemetryMetricGetter m = new TelemetryMetricGetter(BulkheadMetricBean.class, "waitForAsync");
m.baselineMetrics();

CompletableFuture<Void> waitingFuture = newWaitingFuture();

Future<?> f1 = bulkheadBean.waitForAsync(waitingFuture);
Future<?> f2 = bulkheadBean.waitForAsync(waitingFuture);
bulkheadBean.waitForRunningExecutions(2);

Future<?> f3 = bulkheadBean.waitForAsync(waitingFuture);
Future<?> f4 = bulkheadBean.waitForAsync(waitingFuture);
waitForQueuePopulation(m, 2, config.getTimeoutInMillis(2000));

expectBulkheadException(bulkheadBean.waitForAsync(waitingFuture));

assertThat("executions running", m.getBulkheadExecutionsRunning().value(), is(2L));
assertThat("executions waiting", m.getBulkheadExecutionsWaiting().value(), is(2L));

Thread.sleep(config.getTimeoutInMillis(1000));
waitingFuture.complete(null);

f1.get(1, MINUTES);
f2.get(1, MINUTES);
f3.get(1, MINUTES);
f4.get(1, MINUTES);

assertThat("executions running", m.getBulkheadExecutionsRunning().value(), is(0L));
assertThat("accepted calls", m.getBulkheadCalls(ACCEPTED).delta(), is(4L));
assertThat("rejections", m.getBulkheadCalls(REJECTED).delta(), is(1L));

Long queueWaits = m.getBulkheadWaitingDuration().getHistogramCount().get();

// Expect 2 * wait for 0ms, 2 * wait for durationms
assertThat("waiting duration histogram counts", queueWaits, is(4L));

// General metrics should be updated
assertThat("successful invocations", m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(),
is(4L));
assertThat("failed invocations", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(),
is(1L));
}

@Test(dependsOnGroups = "main")
public void testMetricUnits() throws InterruptedException, ExecutionException {
InMemoryMetricReader reader = InMemoryMetricReader.current();

// Validate that each metric has metadata which declares the correct unit
for (TelemetryMetricDefinition metric : TelemetryMetricDefinition.values()) {
if (!metric.getName().startsWith("ft.bulkhead")) {
continue;
}

String unit = reader.getUnit(metric.getName());

if (metric.getUnit() == null) {
assertTrue(unit.isEmpty(), "Unexpected metadata for metric " + metric.getName());
} else {
assertFalse(unit.isEmpty(), "Missing metadata for metric " + metric.getName());
assertEquals(unit, metric.getUnit(), "Incorrect unit for metric " + metric.getName());
}
}
}

private void waitForQueuePopulation(TelemetryMetricGetter m,
int expectedQueuePopulation,
long timeoutInMs) throws InterruptedException {
long timeoutTime = System.currentTimeMillis() + timeoutInMs;
while (System.currentTimeMillis() < timeoutTime) {
if (m.getBulkheadExecutionsWaiting().value() == expectedQueuePopulation) {
return;
}
Thread.sleep(100L);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*
* Copyright (c) 2018-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics;

import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerResult.CIRCUIT_BREAKER_OPEN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerResult.FAILURE;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerResult.SUCCESS;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.EXCEPTION_THROWN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.VALUE_RETURNED;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expect;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expectCbOpen;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expectTestException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

import java.util.concurrent.ExecutionException;

import org.eclipse.microprofile.fault.tolerance.tck.config.ConfigAnnotationAsset;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.CircuitBreakerMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.CircuitBreakerMetricBean.Result;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.CircuitBreakerMetricBean.SkippedException;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.InMemoryMetricReader;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.PullExporterAutoConfigurationCustomizerProvider;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
import org.eclipse.microprofile.fault.tolerance.tck.util.TCKConfig;
import org.eclipse.microprofile.faulttolerance.exceptions.CircuitBreakerOpenException;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import jakarta.inject.Inject;

public class CircuitBreakerTelemetryTest extends Arquillian {

private static final long CB_CLOSE_TIMEOUT = TCKConfig.getConfig().getTimeoutInDuration(5000).toNanos();

@Deployment
public static WebArchive deploy() {

ConfigAnnotationAsset config = new ConfigAnnotationAsset()
.autoscaleMethod(CircuitBreakerMetricBean.class, "doWork");

JavaArchive jar = ShrinkWrap.create(JavaArchive.class, "ftMetricCircuitBreaker.jar")
.addClasses(CircuitBreakerMetricBean.class)
.addPackage(Packages.UTILS)
.addPackage(Packages.TELEMETRY_METRIC_UTILS)
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsManifestResource(config, "microprofile-config.properties");

WebArchive war = ShrinkWrap.create(WebArchive.class, "ftMetricCircuitBreaker.war")
.addAsResource(new StringAsset(
"otel.sdk.disabled=false\notel.traces.exporter=none"),
"META-INF/microprofile-config.properties")
.addAsLibrary(jar)
.addAsServiceProvider(AutoConfigurationCustomizerProvider.class,
PullExporterAutoConfigurationCustomizerProvider.class);

return war;
}

@Inject
private CircuitBreakerMetricBean cbBean;

@BeforeTest
public void closeTheCircuit() throws Exception {

// Condition is needed because BeforeTest runs on both client and server
if (cbBean != null) {

// Assume the circuit is open
// Attempt to put successful work through it until it stops throwing CircuitBreakerOpenExceptions
boolean circuitOpen = true;
long startTime = System.nanoTime();
while (circuitOpen && System.nanoTime() - startTime < CB_CLOSE_TIMEOUT) {
try {
for (int i = 0; i < 2; i++) {
cbBean.doWork(Result.PASS);
}
circuitOpen = false;
} catch (CircuitBreakerOpenException e) {
Thread.sleep(100);
}
}

if (circuitOpen) {
throw new RuntimeException("Timed out waiting for circuit breaker to close");
}
}
}

@Test(groups = "main")
public void testCircuitBreakerMetric() throws Exception {
TelemetryMetricGetter m = new TelemetryMetricGetter(CircuitBreakerMetricBean.class, "doWork");

// First failure, circuit remains closed
expectTestException(() -> cbBean.doWork(Result.FAIL));

assertThat("circuitbreaker calls succeeded", m.getCircuitBreakerCalls(SUCCESS).delta(), is(0L));
assertThat("circuitbreaker calls failed", m.getCircuitBreakerCalls(FAILURE).delta(), is(1L));
assertThat("circuitbreaker calls prevented", m.getCircuitBreakerCalls(CIRCUIT_BREAKER_OPEN).delta(), is(0L));
assertThat("circuit breaker times opened", m.getCircuitBreakerOpened().delta(), is(0L));

// Second failure
expectTestException(() -> cbBean.doWork(Result.FAIL));

assertThat("circuitbreaker calls succeeded", m.getCircuitBreakerCalls(SUCCESS).delta(), is(0L));
assertThat("circuitbreaker calls failed", m.getCircuitBreakerCalls(FAILURE).delta(), is(2L));
assertThat("circuitbreaker calls prevented", m.getCircuitBreakerCalls(CIRCUIT_BREAKER_OPEN).delta(), is(0L));
assertThat("circuit breaker times opened", m.getCircuitBreakerOpened().delta(), is(1L));

// Circuit is open, causing failure
expectCbOpen(() -> cbBean.doWork(Result.PASS));

assertThat("circuitbreaker calls succeeded", m.getCircuitBreakerCalls(SUCCESS).delta(), is(0L));
assertThat("circuitbreaker calls failed", m.getCircuitBreakerCalls(FAILURE).delta(), is(2L));
assertThat("circuitbreaker calls prevented", m.getCircuitBreakerCalls(CIRCUIT_BREAKER_OPEN).delta(), is(1L));
assertThat("circuit breaker times opened", m.getCircuitBreakerOpened().delta(), is(1L));

// Wait a while for the circuit to be half-open
Thread.sleep(TCKConfig.getConfig().getTimeoutInMillis(5000));

// Lots of successful work, causing the circuit to close again
for (int i = 0; i < 2; i++) {
cbBean.doWork(Result.PASS);
}

assertThat("circuitbreaker calls succeeded", m.getCircuitBreakerCalls(SUCCESS).delta(), is(2L));
assertThat("circuitbreaker calls failed", m.getCircuitBreakerCalls(FAILURE).delta(), is(2L));
assertThat("circuitbreaker calls prevented", m.getCircuitBreakerCalls(CIRCUIT_BREAKER_OPEN).delta(), is(1L));
assertThat("circuit breaker times opened", m.getCircuitBreakerOpened().delta(), is(1L));

// exception that is considered a success
expect(RuntimeException.class, () -> cbBean.doWork(Result.PASS_EXCEPTION));

assertThat("circuitbreaker calls succeeded", m.getCircuitBreakerCalls(SUCCESS).delta(), is(3L));
assertThat("circuitbreaker calls failed", m.getCircuitBreakerCalls(FAILURE).delta(), is(2L));
assertThat("circuitbreaker calls prevented", m.getCircuitBreakerCalls(CIRCUIT_BREAKER_OPEN).delta(), is(1L));
assertThat("circuit breaker times opened", m.getCircuitBreakerOpened().delta(), is(1L));

// skipped exception also considered a success
expect(SkippedException.class, () -> cbBean.doWork(Result.SKIPPED_EXCEPTION));

assertThat("circuitbreaker calls succeeded", m.getCircuitBreakerCalls(SUCCESS).delta(), is(4L));
assertThat("circuitbreaker calls failed", m.getCircuitBreakerCalls(FAILURE).delta(), is(2L));
assertThat("circuitbreaker calls prevented", m.getCircuitBreakerCalls(CIRCUIT_BREAKER_OPEN).delta(), is(1L));
assertThat("circuit breaker times opened", m.getCircuitBreakerOpened().delta(), is(1L));

// General metrics should be updated
assertThat("successful invocations", m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(),
is(2L));
assertThat("failed invocations", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(),
is(5L));
}

@Test(dependsOnGroups = "main")
public void testMetricUnits() throws InterruptedException, ExecutionException {
InMemoryMetricReader reader = InMemoryMetricReader.current();

// Validate that each metric has metadata which declares the correct unit
for (TelemetryMetricDefinition metric : TelemetryMetricDefinition.values()) {
if (!metric.getName().startsWith("ft.circuitbreaker")) {
continue;
}

String unit = reader.getUnit(metric.getName());

if (metric.getUnit() == null) {
assertTrue(unit.isEmpty(), "Unexpected metadata for metric " + metric.getName());
} else {
assertFalse(unit.isEmpty(), "Missing metadata for metric " + metric.getName());
assertEquals(unit, metric.getUnit(), "Incorrect unit for metric " + metric.getName());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2018-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics;

import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.VALUE_RETURNED;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;

import java.util.concurrent.ExecutionException;

import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.ClashingNameBean;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.PullExporterAutoConfigurationCustomizerProvider;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.Test;

import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import jakarta.inject.Inject;

public class ClashingNameTelemetryTest extends Arquillian {

@Deployment
public static WebArchive deploy() {
WebArchive war = ShrinkWrap.create(WebArchive.class, "ftMetricClash.war")
.addClasses(ClashingNameBean.class)
.addPackage(Packages.TELEMETRY_METRIC_UTILS)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsResource(new StringAsset(
"otel.sdk.disabled=false\notel.traces.exporter=none"),
"META-INF/microprofile-config.properties")
.addAsServiceProvider(AutoConfigurationCustomizerProvider.class,
PullExporterAutoConfigurationCustomizerProvider.class);

return war;
}

@Inject
private ClashingNameBean clashingNameBean;

@Test
public void testClashingName() throws InterruptedException, ExecutionException {
TelemetryMetricGetter m = new TelemetryMetricGetter(ClashingNameBean.class, "doWork");
m.baselineMetrics();

clashingNameBean.doWork().get();
clashingNameBean.doWork("dummy").get();

assertThat("invocations", m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_APPLIED).delta(),
is(greaterThan(0L)));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright (c) 2018-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics;

import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.EXCEPTION_THROWN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.VALUE_RETURNED;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expectTestException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.ClassLevelMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.PullExporterAutoConfigurationCustomizerProvider;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.RetryResult;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.RetryRetried;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.Test;

import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import jakarta.inject.Inject;

/**
* Ensure that metrics are created correctly when a Fault Tolerance annotation is placed on the class rather than the
* method.
*/
public class ClassLevelTelemetryTest extends Arquillian {

@Deployment
public static WebArchive deploy() {
WebArchive war = ShrinkWrap.create(WebArchive.class, "ftMetricClassLevel.war")
.addClasses(ClassLevelMetricBean.class)
.addPackage(Packages.UTILS)
.addPackage(Packages.TELEMETRY_METRIC_UTILS)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsResource(new StringAsset(
"otel.sdk.disabled=false\notel.traces.exporter=none"),
"META-INF/microprofile-config.properties")
.addAsServiceProvider(AutoConfigurationCustomizerProvider.class,
PullExporterAutoConfigurationCustomizerProvider.class);

return war;
}
@Inject
private ClassLevelMetricBean classLevelRetryBean;

@Test
public void testRetryMetricSuccessfulImmediately() {
TelemetryMetricGetter m = new TelemetryMetricGetter(ClassLevelMetricBean.class, "failSeveralTimes");
m.baselineMetrics();

classLevelRetryBean.failSeveralTimes(0); // Should succeed on first attempt

assertRetryCallsIncremented(m, RetryRetried.FALSE, RetryResult.VALUE_RETURNED, 1L);
assertThat("retries", m.getRetryRetries().delta(), is(0L));

assertThat("invocations returning value",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(), is(1L));
assertThat("invocations throwing exception",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(), is(0L));
}

@Test
public void testRetryMetricSuccessfulAfterRetry() {
TelemetryMetricGetter m = new TelemetryMetricGetter(ClassLevelMetricBean.class, "failSeveralTimes");
m.baselineMetrics();

classLevelRetryBean.failSeveralTimes(3); // Should retry 3 times, and eventually succeed

assertRetryCallsIncremented(m, RetryRetried.TRUE, RetryResult.VALUE_RETURNED, 1L);
assertThat("retries", m.getRetryRetries().delta(), is(3L));

assertThat("invocations returning value",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(), is(1L));
assertThat("invocations throwing exception",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(), is(0L));
}

@Test
public void testRetryMetricUnsuccessful() {
TelemetryMetricGetter m = new TelemetryMetricGetter(ClassLevelMetricBean.class, "failSeveralTimes");
m.baselineMetrics();

expectTestException(() -> classLevelRetryBean.failSeveralTimes(20)); // Should retry 5 times, then fail
expectTestException(() -> classLevelRetryBean.failSeveralTimes(20)); // Should retry 5 times, then fail

assertRetryCallsIncremented(m, RetryRetried.TRUE, RetryResult.MAX_RETRIES_REACHED, 2L);
assertThat("retries", m.getRetryRetries().delta(), is(10L));

assertThat("invocations returning value",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(), is(0L));
assertThat("invocations throwing exception",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(), is(2L));
}

private void assertRetryCallsIncremented(TelemetryMetricGetter m, RetryRetried retriedValue,
RetryResult resultValue,
Long expectedDelta) {
for (RetryRetried retried : RetryRetried.values()) {
for (RetryResult result : RetryResult.values()) {
if (retried == retriedValue && result == resultValue) {
assertThat("Retry calls (" + retried + ", " + result + ")",
m.getRetryCalls(retried, result).delta(), is(expectedDelta));
} else {
assertThat("Retry calls (" + retried + ", " + result + ")",
m.getRetryCalls(retried, result).delta(), is(0L));
}
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/*
* Copyright (c) 2018-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics;

import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.EXCEPTION_THROWN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.VALUE_RETURNED;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expectTestException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.expectThrows;

import java.util.concurrent.ExecutionException;

import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.FallbackMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.FallbackMetricBean.Action;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.FallbackMetricBean.NonFallbackException;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.FallbackMetricHandler;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.InMemoryMetricReader;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.PullExporterAutoConfigurationCustomizerProvider;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.Test;

import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import jakarta.inject.Inject;

public class FallbackTelemetryTest extends Arquillian {

@Deployment
public static WebArchive deploy() {
WebArchive war = ShrinkWrap.create(WebArchive.class, "ftMetricFallback.war")
.addClasses(FallbackMetricBean.class, FallbackMetricHandler.class)
.addPackage(Packages.UTILS)
.addPackage(Packages.TELEMETRY_METRIC_UTILS)
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsResource(new StringAsset(
"otel.sdk.disabled=false\notel.traces.exporter=none"),
"META-INF/microprofile-config.properties")
.addAsServiceProvider(AutoConfigurationCustomizerProvider.class,
PullExporterAutoConfigurationCustomizerProvider.class);
return war;
}

@Inject
private FallbackMetricBean fallbackBean;

@Test(groups = "main")
public void fallbackMetricMethodTest() {
TelemetryMetricGetter m = new TelemetryMetricGetter(FallbackMetricBean.class, "doWork");
m.baselineMetrics();

fallbackBean.setFallbackAction(Action.PASS);
fallbackBean.doWork(Action.PASS);

assertThat("successful without fallback",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_APPLIED).delta(), is(1L));
assertThat("successful with fallback", m.getInvocations(VALUE_RETURNED, InvocationFallback.APPLIED).delta(),
is(0L));
assertThat("failed without fallback",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_APPLIED).delta(), is(0L));
assertThat("failed with fallback", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.APPLIED).delta(),
is(0L));

fallbackBean.doWork(Action.FAIL);

assertThat("successful without fallback",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_APPLIED).delta(), is(1L));
assertThat("successful with fallback", m.getInvocations(VALUE_RETURNED, InvocationFallback.APPLIED).delta(),
is(1L));
assertThat("failed without fallback",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_APPLIED).delta(), is(0L));
assertThat("failed with fallback", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.APPLIED).delta(),
is(0L));

fallbackBean.setFallbackAction(Action.FAIL);
expectTestException(() -> fallbackBean.doWork(Action.FAIL));

assertThat("successful without fallback",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_APPLIED).delta(), is(1L));
assertThat("successful with fallback", m.getInvocations(VALUE_RETURNED, InvocationFallback.APPLIED).delta(),
is(1L));
assertThat("failed without fallback",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_APPLIED).delta(), is(0L));
assertThat("failed with fallback", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.APPLIED).delta(),
is(1L));

fallbackBean.setFallbackAction(Action.PASS);
expectThrows(NonFallbackException.class, () -> fallbackBean.doWork(Action.NON_FALLBACK_EXCEPTION));

assertThat("successful without fallback",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_APPLIED).delta(), is(1L));
assertThat("successful with fallback", m.getInvocations(VALUE_RETURNED, InvocationFallback.APPLIED).delta(),
is(1L));
assertThat("failed without fallback",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_APPLIED).delta(), is(1L));
assertThat("failed with fallback", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.APPLIED).delta(),
is(1L));
}

@Test(groups = "main")
public void fallbackMetricHandlerTest() {
TelemetryMetricGetter m = new TelemetryMetricGetter(FallbackMetricBean.class, "doWorkWithHandler");
m.baselineMetrics();

fallbackBean.setFallbackAction(Action.PASS);
fallbackBean.doWorkWithHandler(Action.PASS);

assertThat("successful without fallback",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_APPLIED).delta(), is(1L));
assertThat("successful with fallback", m.getInvocations(VALUE_RETURNED, InvocationFallback.APPLIED).delta(),
is(0L));
assertThat("failed without fallback",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_APPLIED).delta(), is(0L));
assertThat("failed with fallback", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.APPLIED).delta(),
is(0L));

fallbackBean.doWorkWithHandler(Action.FAIL);

assertThat("successful without fallback",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_APPLIED).delta(), is(1L));
assertThat("successful with fallback", m.getInvocations(VALUE_RETURNED, InvocationFallback.APPLIED).delta(),
is(1L));
assertThat("failed without fallback",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_APPLIED).delta(), is(0L));
assertThat("failed with fallback", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.APPLIED).delta(),
is(0L));

fallbackBean.setFallbackAction(Action.FAIL);
expectTestException(() -> fallbackBean.doWorkWithHandler(Action.FAIL));

assertThat("successful without fallback",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_APPLIED).delta(), is(1L));
assertThat("successful with fallback", m.getInvocations(VALUE_RETURNED, InvocationFallback.APPLIED).delta(),
is(1L));
assertThat("failed without fallback",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_APPLIED).delta(), is(0L));
assertThat("failed with fallback", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.APPLIED).delta(),
is(1L));

fallbackBean.setFallbackAction(Action.PASS);
expectThrows(NonFallbackException.class, () -> fallbackBean.doWorkWithHandler(Action.NON_FALLBACK_EXCEPTION));

assertThat("successful without fallback",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_APPLIED).delta(), is(1L));
assertThat("successful with fallback", m.getInvocations(VALUE_RETURNED, InvocationFallback.APPLIED).delta(),
is(1L));
assertThat("failed without fallback",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_APPLIED).delta(), is(1L));
assertThat("failed with fallback", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.APPLIED).delta(),
is(1L));
}

@Test(dependsOnGroups = "main")
public void testMetricUnits() throws InterruptedException, ExecutionException {
InMemoryMetricReader reader = InMemoryMetricReader.current();

// Validate that each metric has metadata which declares the correct unit
for (TelemetryMetricDefinition metric : TelemetryMetricDefinition.values()) {
if (!metric.getName().equals("ft.invocations.total")) {
continue;
}

String unit = reader.getUnit(metric.getName());

if (metric.getUnit() == null) {
assertTrue(unit.isEmpty(), "Unexpected metadata for metric " + metric.getName());
} else {
assertFalse(unit.isEmpty(), "Missing metadata for metric " + metric.getName());
assertEquals(unit, metric.getUnit(), "Incorrect unit for metric " + metric.getName());
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright (c) 2018-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics;

import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.BulkheadResult.ACCEPTED;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.BulkheadResult.REJECTED;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerResult.CIRCUIT_BREAKER_OPEN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerResult.FAILURE;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerResult.SUCCESS;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerState.CLOSED;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerState.HALF_OPEN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.EXCEPTION_THROWN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.VALUE_RETURNED;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

import java.util.concurrent.ExecutionException;

import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.AllMetricsBean;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.PullExporterAutoConfigurationCustomizerProvider;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.RetryResult;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.RetryRetried;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.TimeoutTimedOut;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.Test;

import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import jakarta.inject.Inject;

/**
* Check that metrics are not added when disabled by a config parameter
*/
public class FaultToleranceDisabledTelemetryTest extends Arquillian {

@Deployment
public static WebArchive deploy() {
WebArchive war = ShrinkWrap.create(WebArchive.class, "ftMetricDisabled.war")
.addClasses(AllMetricsBean.class)
.addPackage(Packages.UTILS)
.addPackage(Packages.TELEMETRY_METRIC_UTILS)
.addAsResource(new StringAsset(
"otel.sdk.disabled=false\notel.traces.exporter=none\nMP_Fault_Tolerance_Metrics_Enabled=false"),
"META-INF/microprofile-config.properties")
.addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsServiceProvider(AutoConfigurationCustomizerProvider.class,
PullExporterAutoConfigurationCustomizerProvider.class);

return war;
}

@Inject
private AllMetricsBean allMetricsBean;

@Test
public void testMetricsDisabled() throws InterruptedException, ExecutionException {
TelemetryMetricGetter m = new TelemetryMetricGetter(AllMetricsBean.class, "doWork");
m.baselineMetrics();

allMetricsBean.doWork().get(); // Should succeed on first attempt

// General metrics
assertThat("successful without fallback",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_APPLIED).delta(), is(0L));
assertThat("successful with fallback", m.getInvocations(VALUE_RETURNED, InvocationFallback.APPLIED).delta(),
is(0L));
assertThat("failed without fallback",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_APPLIED).delta(), is(0L));
assertThat("failed with fallback", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.APPLIED).delta(),
is(0L));

// Retry metrics
assertThat("value returned, no retry", m.getRetryCalls(RetryRetried.FALSE, RetryResult.VALUE_RETURNED).delta(),
is(0L));
assertThat("exception thrown, no retry",
m.getRetryCalls(RetryRetried.FALSE, RetryResult.EXCEPTION_NOT_RETRYABLE).delta(), is(0L));
assertThat("max retries reached, no retry",
m.getRetryCalls(RetryRetried.FALSE, RetryResult.MAX_RETRIES_REACHED).delta(), is(0L));
assertThat("max duration reached, no retry",
m.getRetryCalls(RetryRetried.FALSE, RetryResult.MAX_DURATION_REACHED).delta(), is(0L));
assertThat("value returned after retry", m.getRetryCalls(RetryRetried.TRUE, RetryResult.VALUE_RETURNED).delta(),
is(0L));
assertThat("exception thrown after retry",
m.getRetryCalls(RetryRetried.TRUE, RetryResult.EXCEPTION_NOT_RETRYABLE).delta(), is(0L));
assertThat("max retries reached after retry",
m.getRetryCalls(RetryRetried.TRUE, RetryResult.MAX_RETRIES_REACHED).delta(), is(0L));
assertThat("max duration reached after retry",
m.getRetryCalls(RetryRetried.TRUE, RetryResult.MAX_DURATION_REACHED).delta(), is(0L));
assertThat("retries", m.getRetryRetries().delta(), is(0L));

// Timeout metrics
assertThat("timeout execution duration histogram present", m.getTimeoutExecutionDuration().isPresent(),
is(false));
assertThat("timed out calls", m.getTimeoutCalls(TimeoutTimedOut.TRUE).delta(), is(0L));
assertThat("non timed out calls", m.getTimeoutCalls(TimeoutTimedOut.FALSE).delta(), is(0L));

// CircuitBreaker metrics
assertThat("circuitbreaker succeeded calls", m.getCircuitBreakerCalls(SUCCESS).delta(), is(0L));
assertThat("circuitbreaker failed calls", m.getCircuitBreakerCalls(FAILURE).delta(), is(0L));
assertThat("circuitbreaker prevented calls", m.getCircuitBreakerCalls(CIRCUIT_BREAKER_OPEN).delta(), is(0L));
assertThat("circuitbreaker closed time", m.getCircuitBreakerState(CLOSED).delta(), is(0L));
assertThat("circuitbreaker half open time", m.getCircuitBreakerState(HALF_OPEN).delta(), is(0L));
assertThat("circuitbreaker open time", m.getCircuitBreakerState(CLOSED).delta(), is(0L));
assertThat("circuitbreaker times opened", m.getCircuitBreakerOpened().delta(), is(0L));

// Bulkhead metrics
assertThat("bulkhead accepted calls", m.getBulkheadCalls(ACCEPTED).delta(), is(0L));
assertThat("bulkhead rejected calls", m.getBulkheadCalls(REJECTED).delta(), is(0L));
assertThat("bulkhead executions running present", m.getBulkheadExecutionsRunning().isPresent(),
is(false));
assertThat("bulkhead executions running value", m.getBulkheadExecutionsRunning().value(), is(0L));
assertThat("bulkhead running duration histogram present", m.getBulkheadRunningDuration().isPresent(),
is(false));
assertThat("bulkhead executions waiting present", m.getBulkheadExecutionsWaiting().isPresent(),
is(false));
assertThat("bulkhead executions waiting value", m.getBulkheadExecutionsWaiting().value(), is(0L));
assertThat("bulkhead queue wait time histogram present", m.getBulkheadWaitingDuration().isPresent(),
is(false));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
/*
* Copyright (c) 2018-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics;

import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.EXCEPTION_THROWN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.VALUE_RETURNED;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expectTestException;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.expectThrows;

import java.time.Duration;
import java.util.Properties;
import java.util.concurrent.ExecutionException;

import org.eclipse.microprofile.fault.tolerance.tck.config.ConfigAnnotationAsset;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.RetryMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.RetryMetricBean.CallCounter;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.RetryMetricBean.NonRetryableException;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.InMemoryMetricReader;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.PullExporterAutoConfigurationCustomizerProvider;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.RetryResult;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.RetryRetried;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
import org.eclipse.microprofile.fault.tolerance.tck.util.TCKConfig;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.Test;

import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import jakarta.inject.Inject;

public class RetryTelemetryTest extends Arquillian {

@Deployment
public static WebArchive deploy() {

Properties props = new Properties();
props.put("otel.sdk.disabled", "false");
props.put("otel.traces.exporter", "none");

ConfigAnnotationAsset config = new ConfigAnnotationAsset()
.autoscaleMethod(RetryMetricBean.class, "failAfterDelay")
.mergeProperties(props); // Scale maxDuration

JavaArchive jar = ShrinkWrap.create(JavaArchive.class, "ftMetricRetry.jar")
.addClasses(RetryMetricBean.class)
.addPackage(Packages.UTILS)
.addPackage(Packages.TELEMETRY_METRIC_UTILS)
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsManifestResource(config, "microprofile-config.properties")
.addAsServiceProvider(AutoConfigurationCustomizerProvider.class,
PullExporterAutoConfigurationCustomizerProvider.class);

WebArchive war = ShrinkWrap.create(WebArchive.class, "ftMetricRetry.war")
.addAsLibrary(jar);

return war;
}

@Inject
private RetryMetricBean retryBean;

@Test(groups = "main")
public void testRetryMetricSuccessfulImmediately() {
TelemetryMetricGetter m = new TelemetryMetricGetter(RetryMetricBean.class, "failSeveralTimes");
m.baselineMetrics();

retryBean.failSeveralTimes(0, new CallCounter()); // Should succeed on first attempt

assertRetryCallsIncremented(m, RetryRetried.FALSE, RetryResult.VALUE_RETURNED, 1L);
assertThat("retries", m.getRetryRetries().delta(), is(0L));

assertThat("invocations returning value",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(), is(1L));
assertThat("invocations throwing exception",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(), is(0L));
}

@Test(groups = "main")
public void testRetryMetricSuccessfulAfterRetry() {
TelemetryMetricGetter m = new TelemetryMetricGetter(RetryMetricBean.class, "failSeveralTimes");
m.baselineMetrics();

retryBean.failSeveralTimes(3, new CallCounter()); // Should retry 3 times, and eventually succeed

assertRetryCallsIncremented(m, RetryRetried.TRUE, RetryResult.VALUE_RETURNED, 1L);
assertThat("retries", m.getRetryRetries().delta(), is(3L));

assertThat("invocations returning value",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(), is(1L));
assertThat("invocations throwing exception",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(), is(0L));
}

@Test(groups = "main")
public void testRetryMetricNonRetryableImmediately() {
TelemetryMetricGetter m = new TelemetryMetricGetter(RetryMetricBean.class, "failSeveralTimesThenNonRetryable");
m.baselineMetrics();

// Should throw non-retryable exception on first attempt
expectThrows(NonRetryableException.class,
() -> retryBean.failSeveralTimesThenNonRetryable(0, new CallCounter()));

assertRetryCallsIncremented(m, RetryRetried.FALSE, RetryResult.EXCEPTION_NOT_RETRYABLE, 1L);
assertThat("retries", m.getRetryRetries().delta(), is(0L));

assertThat("invocations returning value",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(), is(0L));
assertThat("invocations throwing exception",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(), is(1L));
}

@Test(groups = "main")
public void testRetryMetricNonRetryableAfterRetries() {
TelemetryMetricGetter m = new TelemetryMetricGetter(RetryMetricBean.class, "failSeveralTimesThenNonRetryable");
m.baselineMetrics();

// Should throw non-retryable exception after 3 retries
expectThrows(NonRetryableException.class,
() -> retryBean.failSeveralTimesThenNonRetryable(3, new CallCounter()));

assertRetryCallsIncremented(m, RetryRetried.TRUE, RetryResult.EXCEPTION_NOT_RETRYABLE, 1L);
assertThat("retries", m.getRetryRetries().delta(), is(3L));

assertThat("invocations returning value",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(), is(0L));
assertThat("invocations throwing exception",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(), is(1L));
}

@Test(groups = "main")
public void testRetryMetricMaxRetries() {
TelemetryMetricGetter m = new TelemetryMetricGetter(RetryMetricBean.class, "failSeveralTimes");
m.baselineMetrics();

expectTestException(() -> retryBean.failSeveralTimes(20, new CallCounter())); // Should retry 5 times, then fail
expectTestException(() -> retryBean.failSeveralTimes(20, new CallCounter())); // Should retry 5 times, then fail

assertRetryCallsIncremented(m, RetryRetried.TRUE, RetryResult.MAX_RETRIES_REACHED, 2L);
assertThat("retries", m.getRetryRetries().delta(), is(10L));

assertThat("invocations returning value",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(), is(0L));
assertThat("invocations throwing exception",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(), is(2L));
}

@Test(groups = "main")
public void testRetryMetricMaxRetriesHitButNoRetry() {
// This is an edge case which can only occur when maxRetries = 0
TelemetryMetricGetter m = new TelemetryMetricGetter(RetryMetricBean.class, "maxRetriesZero");
m.baselineMetrics();

expectTestException(() -> retryBean.maxRetriesZero()); // Should fail immediately and not retry

assertRetryCallsIncremented(m, RetryRetried.FALSE, RetryResult.MAX_RETRIES_REACHED, 1L);
assertThat("retries", m.getRetryRetries().delta(), is(0L));

assertThat("invocations returning value",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(), is(0L));
assertThat("invocations throwing exception",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(), is(1L));
}

@Test(groups = "main")
public void testRetryMetricMaxDuration() {
TelemetryMetricGetter m = new TelemetryMetricGetter(RetryMetricBean.class, "failAfterDelay");
m.baselineMetrics();

Duration testDelay = TCKConfig.getConfig().getTimeoutInDuration(100);
expectTestException(() -> retryBean.failAfterDelay(testDelay)); // Should retry ~10 times, then reach max
// duration

assertRetryCallsIncremented(m, RetryRetried.TRUE, RetryResult.MAX_DURATION_REACHED, 1L);

assertThat("invocations returning value",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(), is(0L));
assertThat("invocations throwing exception",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(), is(1L));
}

@Test(groups = "main")
public void testRetryMetricMaxDurationNoRetries() {
TelemetryMetricGetter m = new TelemetryMetricGetter(RetryMetricBean.class, "failAfterDelay");
m.baselineMetrics();

Duration testDelay = TCKConfig.getConfig().getTimeoutInDuration(1500);
expectTestException(() -> retryBean.failAfterDelay(testDelay)); // Should fail after first attempt due to
// reaching maxDuration

assertRetryCallsIncremented(m, RetryRetried.FALSE, RetryResult.MAX_DURATION_REACHED, 1L);
assertThat("retries", m.getRetryRetries().delta(), is(0L));

assertThat("invocations returning value",
m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(), is(0L));
assertThat("invocations throwing exception",
m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(), is(1L));
}

@Test(dependsOnGroups = "main")
public void testMetricUnits() throws InterruptedException, ExecutionException {
InMemoryMetricReader reader = InMemoryMetricReader.current();

// Validate that each metric has metadata which declares the correct unit
for (TelemetryMetricDefinition metric : TelemetryMetricDefinition.values()) {
if (!metric.getName().startsWith("ft.retry")) {
continue;
}

String unit = reader.getUnit(metric.getName());

if (metric.getUnit() == null) {
assertTrue(unit.isEmpty(), "Unexpected metadata for metric " + metric.getName());
} else {
assertFalse(unit.isEmpty(), "Missing metadata for metric " + metric.getName());
assertEquals(unit, metric.getUnit(), "Incorrect unit for metric " + metric.getName());
}
}
}

private void assertRetryCallsIncremented(TelemetryMetricGetter m, RetryRetried retriedValue,
RetryResult resultValue,
Long expectedDelta) {
for (RetryRetried retried : RetryRetried.values()) {
for (RetryResult result : RetryResult.values()) {
if (retried == retriedValue && result == resultValue) {
assertThat("Retry calls (" + retried + ", " + result + ")",
m.getRetryCalls(retried, result).delta(), is(expectedDelta));
} else {
assertThat("Retry calls (" + retried + ", " + result + ")",
m.getRetryCalls(retried, result).delta(), is(0L));
}
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2018-2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics;

import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.EXCEPTION_THROWN;
import static org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult.VALUE_RETURNED;
import static org.eclipse.microprofile.fault.tolerance.tck.util.Exceptions.expectTimeout;
import static org.eclipse.microprofile.fault.tolerance.tck.util.TCKConfig.getConfig;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

import java.util.Properties;
import java.util.concurrent.ExecutionException;

import org.eclipse.microprofile.fault.tolerance.tck.config.ConfigAnnotationAsset;
import org.eclipse.microprofile.fault.tolerance.tck.metrics.common.TimeoutMetricBean;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.InMemoryMetricReader;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.PullExporterAutoConfigurationCustomizerProvider;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationFallback;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.TimeoutTimedOut;
import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricGetter;
import org.eclipse.microprofile.fault.tolerance.tck.util.Packages;
import org.eclipse.microprofile.faulttolerance.Timeout;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.testng.Arquillian;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.testng.annotations.Test;

import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import jakarta.inject.Inject;

public class TimeoutTelemetryTest extends Arquillian {

@Deployment
public static WebArchive deploy() {
Properties props = new Properties();
props.put("otel.sdk.disabled", "false");
props.put("otel.traces.exporter", "none");

final ConfigAnnotationAsset config = new ConfigAnnotationAsset()
.setValue(TimeoutMetricBean.class, "counterTestWorkForMillis", Timeout.class,
getConfig().getTimeoutInStr(500))
.setValue(TimeoutMetricBean.class, "histogramTestWorkForMillis", Timeout.class,
getConfig().getTimeoutInStr(2000))
.mergeProperties(props);

JavaArchive jar = ShrinkWrap.create(JavaArchive.class, "ftMetricTimeout.jar")
.addClasses(TimeoutMetricBean.class)
.addPackage(Packages.UTILS)
.addPackage(Packages.TELEMETRY_METRIC_UTILS)
.addAsManifestResource(config, "microprofile-config.properties")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")
.addAsServiceProvider(AutoConfigurationCustomizerProvider.class,
PullExporterAutoConfigurationCustomizerProvider.class);

WebArchive war = ShrinkWrap.create(WebArchive.class, "ftMetricTimeout.war")
.addAsLibrary(jar);
return war;
}

@Inject
private TimeoutMetricBean timeoutBean;

@Test(groups = "main")
public void testTimeoutMetric() {
TelemetryMetricGetter m = new TelemetryMetricGetter(TimeoutMetricBean.class, "counterTestWorkForMillis");
m.baselineMetrics();

expectTimeout(() -> timeoutBean.counterTestWorkForMillis(getConfig().getTimeoutInMillis(2000))); // Should
// timeout
expectTimeout(() -> timeoutBean.counterTestWorkForMillis(getConfig().getTimeoutInMillis(2000))); // Should
// timeout
timeoutBean.counterTestWorkForMillis(getConfig().getTimeoutInMillis(100)); // Should not timeout

assertThat("calls timed out", m.getTimeoutCalls(TimeoutTimedOut.TRUE).delta(), is(2L));
assertThat("calls not timed out", m.getTimeoutCalls(TimeoutTimedOut.FALSE).delta(), is(1L));

assertThat("successful invocations", m.getInvocations(VALUE_RETURNED, InvocationFallback.NOT_DEFINED).delta(),
is(1L));
assertThat("failed invocations", m.getInvocations(EXCEPTION_THROWN, InvocationFallback.NOT_DEFINED).delta(),
is(2L));
}

@Test(groups = "main")
public void testTimeoutHistogram() {
TelemetryMetricGetter m = new TelemetryMetricGetter(TimeoutMetricBean.class, "histogramTestWorkForMillis");

timeoutBean.histogramTestWorkForMillis(getConfig().getTimeoutInMillis(300));
expectTimeout(() -> timeoutBean.histogramTestWorkForMillis(getConfig().getTimeoutInMillis(5000))); // Will
// timeout
// after 2000

Long histogramCount = m.getTimeoutExecutionDuration().getHistogramCount().get();
assertThat("Histogram count", histogramCount, is(2L));
}

@Test(dependsOnGroups = "main")
public void testMetricUnits() throws InterruptedException, ExecutionException {
InMemoryMetricReader reader = InMemoryMetricReader.current();

// Validate that each metric has metadata which declares the correct unit
for (TelemetryMetricDefinition metric : TelemetryMetricDefinition.values()) {
if (!metric.getName().startsWith("ft.timeout")) {
continue;
}

String unit = reader.getUnit(metric.getName());

if (metric.getUnit() == null) {
assertTrue(unit.isEmpty(), "Unexpected metadata for metric " + metric.getName());
} else {
assertFalse(unit.isEmpty(), "Missing metadata for metric " + metric.getName());
assertEquals(unit, metric.getUnit(), "Incorrect unit for metric " + metric.getName());
}
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
*******************************************************************************
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util;

import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.stream.Collectors;

import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.metrics.InstrumentType;
import io.opentelemetry.sdk.metrics.data.AggregationTemporality;
import io.opentelemetry.sdk.metrics.data.LongPointData;
import io.opentelemetry.sdk.metrics.data.MetricData;
import io.opentelemetry.sdk.metrics.export.CollectionRegistration;
import io.opentelemetry.sdk.metrics.export.MetricReader;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.spi.CDI;

@ApplicationScoped
public class InMemoryMetricReader implements MetricReader {

private CollectionRegistration collectionRegistration;
private boolean isShutdown = false;

public static InMemoryMetricReader current() {
return CDI.current().select(InMemoryMetricReader.class).get();
}

@Override
public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) {
return AggregationTemporality.CUMULATIVE;
}

@Override
public void register(CollectionRegistration registration) {
if (isShutdown) {
throw new IllegalStateException("InMemoryMetricReader has been shutdown");
}

collectionRegistration = registration;
}

@Override
public CompletableResultCode forceFlush() {
return CompletableResultCode.ofSuccess();
}

@Override
public CompletableResultCode shutdown() {
collectionRegistration = null;
isShutdown = true;
return CompletableResultCode.ofSuccess();
}

public long readLongData(TelemetryMetricID id) {
@SuppressWarnings("unchecked")
List<LongPointData> longData = (List<LongPointData>) getPointData(id);

return longData.stream()
.mapToLong(LongPointData::getValue)
.sum();
}

protected List<?> getPointData(TelemetryMetricID id) {
Collection<MetricData> allMetrics = collectionRegistration.collectAllMetrics();

return allMetrics.stream()
.filter(
md -> md.getName().equals(id.name))
.flatMap(
md -> md.getData().getPoints().stream())
.filter(
point -> id.attributes.asMap().keySet().stream()
.allMatch(key -> point.getAttributes().asMap().containsKey(key)
&& id.attributes.asMap().get(key)
.equals(point.getAttributes().asMap().get(key))))
.collect(Collectors.toList());
}

public Optional<LongPointData> getGaugueMetricLatestValue(TelemetryMetricID id) {
Collection<MetricData> allMetrics = collectionRegistration.collectAllMetrics();

Optional<LongPointData> gague = allMetrics.stream()
.filter(
md -> md.getName().equals(id.name))
.flatMap(md -> md.getLongGaugeData().getPoints().stream())
.filter(point -> id.attributes.asMap().keySet().stream()
.allMatch(key -> point.getAttributes().asMap().containsKey(key)
&& id.attributes.asMap().get(key)
.equals(point.getAttributes().asMap().get(key))))
// feeding the points into Long.compare in reverse order will return the largest first.
.sorted((pointOne, pointTwo) -> Long.compare(pointTwo.getEpochNanos(), pointOne.getEpochNanos()))
.findFirst();

return gague;
}

public String getUnit(String metricName) {
try {
Collection<MetricData> allMetrics = collectionRegistration.collectAllMetrics();
Optional<MetricData> mathcingData = allMetrics.stream()
.filter(
md -> md.getName().equals(metricName))
.findAny();

return mathcingData.get().getUnit();
} catch (NoSuchElementException e) {
// If we didn't find anything throwing an exception to fail the test is reasonable
throw new RuntimeException("Found no results for " + metricName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
*******************************************************************************
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util;

import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.metrics.SdkMeterProviderBuilder;
import jakarta.enterprise.inject.spi.CDI;

public class PullExporterAutoConfigurationCustomizerProvider implements AutoConfigurationCustomizerProvider {

public void customize(AutoConfigurationCustomizer autoConfiguration) {
autoConfiguration.addMeterProviderCustomizer(this::registerMeterProvider);
}

private SdkMeterProviderBuilder registerMeterProvider(SdkMeterProviderBuilder builder,
ConfigProperties properties) {
InMemoryMetricReader exporter = CDI.current().select(InMemoryMetricReader.class).get();
builder.registerMetricReader(exporter);
return builder;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
*******************************************************************************
* Copyright (c) 2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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 org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util;

/**
* Allows tests to get the value of a counter and compare it with a baseline.
* <p>
* Most methods on this class will treat a non-existent counter as having a value of zero to allow implementations to
* lazily create metrics.
* <p>
* Most tests should use {@link TelemetryMetricGetter} to create instances of this class.
*/
public class TelemetryCounterMetric {

private TelemetryMetricID metricId;
private long baseline;

public TelemetryCounterMetric(TelemetryMetricID metricId) {
this.metricId = metricId;
this.baseline = 0;
}

/**
* Get the counter value, or zero if the metric doesn't exist
* <p>
* This method will not create the metric if it does not exist.
*
* @param startTime
* a start time as a unix epoch, all metrics from before this time will be ignored
* @param endTime
* a end time as a unix epoch, all metrics from after this time will be ignored
* @return the counter value, or zero if the metric doesn't exist
*/
public long value() {
return InMemoryMetricReader.current().readLongData(metricId);
}

/**
* Capture the current counter value for later comparison with {@link #delta()}
* <p>
* This method will not create the metric if it does not exist.
*/
public void baseline() {
baseline = value();
}

/**
* Return the difference between the current value of the metric and the value when {@link #baseline} was called.
*
* @return the difference between the metric value and the baseline
*/
public long delta() {
return value() - baseline;
}

}
Loading