diff --git a/spec/src/main/asciidoc/metrics.asciidoc b/spec/src/main/asciidoc/metrics.asciidoc index 42e901e0..6c840826 100644 --- a/spec/src/main/asciidoc/metrics.asciidoc +++ b/spec/src/main/asciidoc/metrics.asciidoc @@ -1,5 +1,5 @@ // -// Copyright (c) 2018-2020 Contributors to the Eclipse Foundation +// Copyright (c) 2018-2024 Contributors to the Eclipse Foundation // // See the NOTICE file(s) distributed with this work for additional // information regarding copyright ownership. @@ -18,12 +18,16 @@ // Contributors: // Andrew Rouse // Jan Bernitt +// Benjamin Confino -== Integration with MicroProfile Metrics +== Integration with MicroProfile Metrics and MicroProfile Telemetry -When Microprofile Fault Tolerance and Microprofile Metrics are used together, metrics are automatically added for each of +When MicroProfile Fault Tolerance is used together with MicroProfile Metrics or MicroProfile Telemetry, metrics are automatically added for each of the methods annotated with a `@Retry`, `@Timeout`, `@CircuitBreaker`, `@Bulkhead` or `@Fallback` annotation. +If all three of MicroProfile Fault Tolerance, MicroProfile Metrics, and MicroProfile Telemetry are used together then MicroProfile Fault Tolerance +exports metrics to both MicroProfile Metrics and MicroProfile Telemetry. + === Names The automatically added metrics follow a consistent pattern which includes the fully qualified name of the annotated method. @@ -33,7 +37,7 @@ is non-portable and may vary between implementations. For portable behavior, mon === Scope -Metrics added by this specification will appear in the `base` MicroProfile Metrics scope. +In MicroProfile Metrics, metrics added by this specification will appear in the `base` MicroProfile Metrics scope. === Registration @@ -44,11 +48,12 @@ Policies that have been disabled through configuration do not cause registration Implementations must ensure that if any of these annotations are present on a method, then the following metrics are added only once for that method. -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.invocations.total` -| Type | `Counter` +| Type in MP Metrics | `Counter` +| Type in MP Telemetry | A counter that emits long | Unit | None | Description | The number of times the method was called | Tags @@ -59,11 +64,12 @@ a| * `method` - the fully qualified method name === Metrics added for `@Retry` -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.retry.calls.total` -| Type | `Counter` +| Type in MP Metrics | `Counter` +| Type in MP Telemetry | A counter that emits long | Unit | None | Description | The number of times the retry logic was run. This will always be once per method call. | Tags @@ -72,11 +78,12 @@ a| * `method` - the fully qualified method name * `retryResult` = `[valueReturned\|exceptionNotRetryable\|maxRetriesReached\|maxDurationReached]` - the reason that last attempt to call the method was not retried |=== -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.retry.retries.total` -| Type | `Counter` +| Type in MP Metrics | `Counter` +| Type in MP Telemetry | A counter that emits long | Unit | None | Description | The number of times the method was retried | Tags @@ -85,11 +92,12 @@ a| * `method` - the fully qualified method name === Metrics added for `@Timeout` -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.timeout.calls.total` -| Type | `Counter` +| Type in MP Metrics | `Counter` +| Type in MP Telemetry | A counter that emits long | Unit | None | Description | The number of times the timeout logic was run. This will usually be once per method call, but may be zero times if the circuit breaker prevents execution or more than once if the method is retried. | Tags @@ -97,11 +105,12 @@ a| * `method` - the fully qualified method name * `timedOut` = `[true\|false]` - whether the method call timed out |=== -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.timeout.executionDuration` -| Type | `Histogram` +| Type in MP Metrics | `Histogram` +| Type in MP Telemetry | A histogram that emits long | Unit | Nanoseconds | Description | Histogram of execution times for the method | Tags @@ -110,11 +119,12 @@ a| * `method` - the fully qualified method name === Metrics added for `@CircuitBreaker` -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.circuitbreaker.calls.total` -| Type | `Counter` +| Type in MP Metrics | `Counter` +| Type in MP Telemetry | A counter that emits long | Unit | None | Description | The number of times the circuit breaker logic was run. This will usually be once per method call, but may be more than once if the method call is retried. | Tags @@ -125,11 +135,12 @@ a| * `method` - the fully qualified method name ** `circuitBreakerOpen` - the method did not run because the circuit breaker was in open or half-open state |=== -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.circuitbreaker.state.total` -| Type | `Gauge` +| Type in MP Metrics | `Gauge` +| Type in MP Telemetry | A counter that emits long | Unit | Nanoseconds | Description | Amount of time the circuit breaker has spent in each state | Tags @@ -138,7 +149,7 @@ a| * `method` - the fully qualified method name | Notes | Although this metric is a `Gauge`, its value increases monotonically. |=== -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.circuitbreaker.opened.total` @@ -151,11 +162,12 @@ a| * `method` - the fully qualified method name === Metrics added for `@Bulkhead` -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.bulkhead.calls.total` -| Type | `Counter` +| Type in MP Metrics | `Counter` +| Type in MP Telemetry | A counter that emits long | Unit | None | Description | The number of times the bulkhead logic was run. This will usually be once per method call, but may be zero times if the circuit breaker prevented execution or more than once if the method call is retried. | Tags @@ -163,22 +175,24 @@ a| * `method` - the fully qualified method name * `bulkheadResult` = `[accepted\|rejected]` - whether the bulkhead allowed the method call to run |=== -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.bulkhead.executionsRunning` -| Type | `Gauge` +| Type in MP Metrics | `Gauge` +| Type in MP Telemetry | A gauge that emits long | Unit | None | Description | Number of currently running executions | Tags a| * `method` - the fully qualified method name |=== -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.bulkhead.executionsWaiting` -| Type | `Gauge` +| Type in MP Metrics | `Gauge` +| Type in MP Telemetry | A gauge that emits long | Unit | None | Description | Number of executions currently waiting in the queue | Tags @@ -186,22 +200,24 @@ a| * `method` - the fully qualified method name | Notes | Only added if the method is also annotated with `@Asynchronous` |=== -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.bulkhead.runningDuration` -| Type | `Histogram` +| Type in MP Metrics | `Histogram` +| Type in MP Telemetry | A histogram that emits long | Unit | Nanoseconds | Description | Histogram of the time that method executions spent running | Tags a| * `method` - the fully qualified method name |=== -[cols="1,5"] +[cols="2,4"] |=== | Name | `ft.bulkhead.waitingDuration` -| Type | `Histogram` +| Type in MP Metrics | `Histogram` +| Type in MP Telemetry | A histogram that emits long | Unit | Nanoseconds | Description | Histogram of the time that method executions spent waiting in the queue | Tags @@ -213,7 +229,7 @@ a| * `method` - the fully qualified method name === Notes Future versions of this specification may change the definitions of the metrics which are added to take advantage of -enhancements in the MicroProfile Metrics specification. +enhancements in the MicroProfile Metrics or MicroProfile Telemetry specification. If more than one annotation is applied to a method, the metrics associated with each annotation will be added for that method. diff --git a/spec/src/main/asciidoc/relationship.asciidoc b/spec/src/main/asciidoc/relationship.asciidoc index ec266971..8c3ab1ee 100644 --- a/spec/src/main/asciidoc/relationship.asciidoc +++ b/spec/src/main/asciidoc/relationship.asciidoc @@ -68,3 +68,11 @@ The MicroProfile Metrics specification provides a way to monitor microservice in * When `Timeout` is used, you would like to know how many times the method timed out. Because of this requirement, when MicroProfile Fault Tolerance and MicroProfile Metrics are used together, metrics are automatically added for each of the methods annotated with a `@Retry`, `@Timeout`, `@CircuitBreaker`, `@Bulkhead` or `@Fallback` annotation. + +=== Relationship to MicroProfile Telemetry +The MicroProfile Telemetry specification provides a way to monitor microservice invocations. It is also important to find out how Fault Tolerance policies are operating, e.g. + +* When `Retry` is used, it is useful to know how many times a method was called and succeeded after retrying at least once. +* When `Timeout` is used, you would like to know how many times the method timed out. + +Because of this requirement, when MicroProfile Fault Tolerance and MicroProfile Telemetry are used together, metrics are automatically added for each of the methods annotated with a `@Retry`, `@Timeout`, `@CircuitBreaker`, `@Bulkhead` or `@Fallback` annotation. diff --git a/tck/formatter.xml b/tck/formatter.xml new file mode 100644 index 00000000..8d902cf1 --- /dev/null +++ b/tck/formatter.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + diff --git a/tck/pom.xml b/tck/pom.xml index 7ffb4244..2e520afd 100644 --- a/tck/pom.xml +++ b/tck/pom.xml @@ -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 @@ 4.0.0 - + org.eclipse.microprofile.fault-tolerance microprofile-fault-tolerance-parent 4.1-SNAPSHOT @@ -46,6 +45,17 @@ + + + + net.revelc.code.formatter + formatter-maven-plugin + + ${project.basedir}/formatter.xml + + + + @@ -55,6 +65,12 @@ provided + + io.opentelemetry + opentelemetry-api + 1.36.0 + + jakarta.enterprise jakarta.enterprise.cdi-api @@ -75,6 +91,18 @@ provided + + io.opentelemetry + opentelemetry-sdk-metrics + 1.36.0 + + + + io.opentelemetry + opentelemetry-sdk-extension-autoconfigure + 1.36.0 + + org.testng testng @@ -109,5 +137,5 @@ - + diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/bulkhead/clientserver/Bulkhead1Retry0MethodSyncBean.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/bulkhead/clientserver/Bulkhead1Retry0MethodSyncBean.java index 26a6d0bf..f179e0b8 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/bulkhead/clientserver/Bulkhead1Retry0MethodSyncBean.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/bulkhead/clientserver/Bulkhead1Retry0MethodSyncBean.java @@ -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(); } diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/bulkhead/clientserver/Bulkhead55RapidRetry10MethodAsynchBean.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/bulkhead/clientserver/Bulkhead55RapidRetry10MethodAsynchBean.java index 63a3c402..a503f2cf 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/bulkhead/clientserver/Bulkhead55RapidRetry10MethodAsynchBean.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/bulkhead/clientserver/Bulkhead55RapidRetry10MethodAsynchBean.java @@ -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); diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/circuitbreaker/clientserver/CircuitBreakerClientWithTimeout.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/circuitbreaker/clientserver/CircuitBreakerClientWithTimeout.java index 9bc3e7f9..36da4364 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/circuitbreaker/clientserver/CircuitBreakerClientWithTimeout.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/circuitbreaker/clientserver/CircuitBreakerClientWithTimeout.java @@ -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 { diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/config/RetryConfigBean.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/config/RetryConfigBean.java index a04db979..73fa80d1 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/config/RetryConfigBean.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/config/RetryConfigBean.java @@ -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) { *

* Limited to 10 seconds or 1000 retries, but will stop as soon as a delay of > 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) { diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/AllMetricsTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/AllMetricsTest.java index 242ba606..7a986b51 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/AllMetricsTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/AllMetricsTest.java @@ -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 diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/BulkheadMetricTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/BulkheadMetricTest.java index d11a759c..a5a493b2 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/BulkheadMetricTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/BulkheadMetricTest.java @@ -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"); diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/CircuitBreakerMetricTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/CircuitBreakerMetricTest.java index 7c298af7..cceb62ab 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/CircuitBreakerMetricTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/CircuitBreakerMetricTest.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClashingNameTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClashingNameTest.java index f382abbe..d2e644ef 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClashingNameTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClashingNameTest.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClassLevelMetricTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClassLevelMetricTest.java index 780fd290..01b1d305 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClassLevelMetricTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClassLevelMetricTest.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/FallbackMetricTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/FallbackMetricTest.java index 31ae6dda..35128289 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/FallbackMetricTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/FallbackMetricTest.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/MetricsDisabledTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/MetricsDisabledTest.java index 90c49b92..9f7c08f1 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/MetricsDisabledTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/MetricsDisabledTest.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/RetryMetricTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/RetryMetricTest.java index 0e5050bc..4049abdf 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/RetryMetricTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/RetryMetricTest.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/TimeoutMetricTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/TimeoutMetricTest.java index 81633f6d..abca2469 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/TimeoutMetricTest.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/TimeoutMetricTest.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/AllMetricsBean.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/AllMetricsBean.java similarity index 96% rename from tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/AllMetricsBean.java rename to tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/AllMetricsBean.java index a87821c2..12dfea3a 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/AllMetricsBean.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/AllMetricsBean.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/BulkheadMetricBean.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/BulkheadMetricBean.java similarity index 97% rename from tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/BulkheadMetricBean.java rename to tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/BulkheadMetricBean.java index 6f6a4530..a91b3734 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/BulkheadMetricBean.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/BulkheadMetricBean.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/CircuitBreakerMetricBean.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/CircuitBreakerMetricBean.java similarity index 96% rename from tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/CircuitBreakerMetricBean.java rename to tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/CircuitBreakerMetricBean.java index 2fe5919b..4c3ff005 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/CircuitBreakerMetricBean.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/CircuitBreakerMetricBean.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClashingNameBean.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/ClashingNameBean.java similarity index 97% rename from tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClashingNameBean.java rename to tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/ClashingNameBean.java index fec7b7f2..3d49dcb1 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClashingNameBean.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/ClashingNameBean.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClassLevelMetricBean.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/ClassLevelMetricBean.java similarity index 95% rename from tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClassLevelMetricBean.java rename to tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/ClassLevelMetricBean.java index 9edeaa72..b787c785 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/ClassLevelMetricBean.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/ClassLevelMetricBean.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/FallbackMetricBean.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/FallbackMetricBean.java similarity index 97% rename from tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/FallbackMetricBean.java rename to tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/FallbackMetricBean.java index 551005db..22c700ea 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/FallbackMetricBean.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/FallbackMetricBean.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/FallbackMetricHandler.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/FallbackMetricHandler.java similarity index 86% rename from tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/FallbackMetricHandler.java rename to tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/FallbackMetricHandler.java index 8b88274b..5da338af 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/FallbackMetricHandler.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/FallbackMetricHandler.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/RetryMetricBean.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/RetryMetricBean.java similarity index 97% rename from tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/RetryMetricBean.java rename to tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/RetryMetricBean.java index 0bcffb54..00d6d974 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/RetryMetricBean.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/RetryMetricBean.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/TimeoutMetricBean.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/TimeoutMetricBean.java similarity index 95% rename from tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/TimeoutMetricBean.java rename to tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/TimeoutMetricBean.java index ebacfd3a..72793849 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/TimeoutMetricBean.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/TimeoutMetricBean.java @@ -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; diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/util/MetricComparator.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/util/TimeUtils.java similarity index 94% rename from tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/util/MetricComparator.java rename to tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/util/TimeUtils.java index c3161353..c16d8520 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/util/MetricComparator.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/common/util/TimeUtils.java @@ -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. *

* 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 approxMillis(final long originalMillis) { * and creates a {@link Matcher} to do the check. *

* 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 diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/util/MetricDefinition.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/util/MetricDefinition.java index 1d15484a..93e80f3b 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/util/MetricDefinition.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/metrics/util/MetricDefinition.java @@ -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 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 getMetricClass() { @@ -122,7 +132,7 @@ public Class getMetricClass() { * The tags which are applied to this metric *

* The classes returned from this method will be enums which implement {@link TagValue} - * + * * @return the tags which are applied to this metric */ public Class[] 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() { } } -} \ No newline at end of file +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/AllAnnotationTelemetryTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/AllAnnotationTelemetryTest.java new file mode 100644 index 00000000..a42ac19c --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/AllAnnotationTelemetryTest.java @@ -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)); + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/BulkheadTelemetryTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/BulkheadTelemetryTest.java new file mode 100644 index 00000000..10180b25 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/BulkheadTelemetryTest.java @@ -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> waitingFutures = new ArrayList<>(); + + /** + * Ensure that any waiting futures get completed at the end of each test + *

+ * Important in case tests end early due to an exception or failure. + */ + @AfterMethod + public void completeWaitingFutures() { + for (CompletableFuture future : waitingFutures) { + future.complete(null); + } + waitingFutures.clear(); + } + + /** + * Use this method to obtain futures for passing to methods on {@link BulkheadMetricBean} + *

+ * Using this factory method ensures they will be completed at the end of the test if your test fails. + */ + private CompletableFuture newWaitingFuture() { + CompletableFuture 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 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 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 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 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 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); + } + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/CircuitBreakerTelemetryTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/CircuitBreakerTelemetryTest.java new file mode 100644 index 00000000..75e598e4 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/CircuitBreakerTelemetryTest.java @@ -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()); + } + } + } +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/ClashingNameTelemetryTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/ClashingNameTelemetryTest.java new file mode 100644 index 00000000..aa2296cb --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/ClashingNameTelemetryTest.java @@ -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))); + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/ClassLevelTelemetryTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/ClassLevelTelemetryTest.java new file mode 100644 index 00000000..e9860842 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/ClassLevelTelemetryTest.java @@ -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)); + } + } + } + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/FallbackTelemetryTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/FallbackTelemetryTest.java new file mode 100644 index 00000000..5eb3ba09 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/FallbackTelemetryTest.java @@ -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()); + } + } + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/FaultToleranceDisabledTelemetryTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/FaultToleranceDisabledTelemetryTest.java new file mode 100644 index 00000000..9d7cf62f --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/FaultToleranceDisabledTelemetryTest.java @@ -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)); + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/RetryTelemetryTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/RetryTelemetryTest.java new file mode 100644 index 00000000..61c79829 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/RetryTelemetryTest.java @@ -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)); + } + } + } + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/TimeoutTelemetryTest.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/TimeoutTelemetryTest.java new file mode 100644 index 00000000..6ee804c8 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/TimeoutTelemetryTest.java @@ -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()); + } + } + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/InMemoryMetricReader.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/InMemoryMetricReader.java new file mode 100644 index 00000000..26bec71e --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/InMemoryMetricReader.java @@ -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 longData = (List) getPointData(id); + + return longData.stream() + .mapToLong(LongPointData::getValue) + .sum(); + } + + protected List getPointData(TelemetryMetricID id) { + Collection 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 getGaugueMetricLatestValue(TelemetryMetricID id) { + Collection allMetrics = collectionRegistration.collectAllMetrics(); + + Optional 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 allMetrics = collectionRegistration.collectAllMetrics(); + Optional 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); + } + } +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/PullExporterAutoConfigurationCustomizerProvider.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/PullExporterAutoConfigurationCustomizerProvider.java new file mode 100644 index 00000000..b872db33 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/PullExporterAutoConfigurationCustomizerProvider.java @@ -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; + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryCounterMetric.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryCounterMetric.java new file mode 100644 index 00000000..871a20f3 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryCounterMetric.java @@ -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. + *

+ * Most methods on this class will treat a non-existent counter as having a value of zero to allow implementations to + * lazily create metrics. + *

+ * 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 + *

+ * 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()} + *

+ * 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; + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryGaugeMetric.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryGaugeMetric.java new file mode 100644 index 00000000..d3e1966c --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryGaugeMetric.java @@ -0,0 +1,85 @@ +/* + ******************************************************************************* + * Copyright (c) 2020-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.util; + +import java.util.Optional; + +import io.opentelemetry.sdk.metrics.data.LongPointData; +import jakarta.enterprise.inject.spi.CDI; + +/** + * Allows tests to get the value of a {@code Gauge} and compare it with a baseline. + *

+ * Most methods on this class will treat a non-existent gauge as having a value of zero to allow implementations to + * lazily create metrics. + *

+ * Most tests should use {@link TelemetryMetricGetter} to create instances of this class. + */ +public class TelemetryGaugeMetric { + private TelemetryMetricID metricId; + private long baseline; + + public TelemetryGaugeMetric(TelemetryMetricID metricId) { + this.metricId = metricId; + this.baseline = 0; + } + + /** + * Get the counter value, or zero if the metric doesn't exist + *

+ * This method will not create the metric if it does not exist. + * + * @return the counter value, or zero if the metric doesn't exist + */ + public long value() { + InMemoryMetricReader reader = CDI.current().select(InMemoryMetricReader.class).get(); + Optional latest = reader.getGaugueMetricLatestValue(metricId); + if (latest.isPresent()) { + return latest.get().getValue(); + } + return 0L; + } + + public boolean isPresent() { + InMemoryMetricReader exporter = CDI.current().select(InMemoryMetricReader.class).get(); + Optional latest = exporter.getGaugueMetricLatestValue(metricId); + return latest.isPresent(); + } + + /** + * Capture the current counter value for later comparison with {@link #delta()} + *

+ * 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; + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryHistogramMetric.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryHistogramMetric.java new file mode 100644 index 00000000..12412f91 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryHistogramMetric.java @@ -0,0 +1,62 @@ +/* + ******************************************************************************* + * Copyright (c) 2020-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.util; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +import io.opentelemetry.sdk.metrics.data.HistogramPointData; + +/** + * Allows tests to get the value of a Histogram, it does not support comparing with a baseline + *

+ * Most tests should use {@link TelemetryMetricGetter} to create instances of this class. + */ +public class TelemetryHistogramMetric { + private final TelemetryMetricID metricId; + + public TelemetryHistogramMetric(TelemetryMetricID metricId) { + this.metricId = metricId; + } + + @SuppressWarnings("unchecked") // Fail early on a misconfigured test + public Collection getHistogramPoints() { + InMemoryMetricReader reader = InMemoryMetricReader.current(); + return (List) reader.getPointData(metricId); + } + + public Optional getHistogramCount() { + Collection hpd = getHistogramPoints(); + + if (hpd.isEmpty()) { + return Optional.empty(); + } + + return Optional.of(hpd.stream().mapToLong( + pd -> pd.getCounts().stream().reduce(0L, Long::sum)) + .sum()); + } + + public boolean isPresent() { + return !getHistogramPoints().isEmpty(); + } +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryMetricDefinition.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryMetricDefinition.java new file mode 100644 index 00000000..145cf31a --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryMetricDefinition.java @@ -0,0 +1,252 @@ +/* + ******************************************************************************* + * 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; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; + +/** + * Enum containing a definition for each metric defined in the spec. + *

+ * All tests should not use this enum directly, but should use {@link TelemetryMetricGetter} to access metric values. + * However, if we add new metrics to the spec, their definitions should be added here. Having a defined list of all + * metrics allows us to easily iterate through them for stuff like {@link TelemetryMetricGetter#baselineMetrics()}. + *

+ * Each metric definition has a name, a type and a set of tags. + *

+ * Each fault tolerance metric is assumed to have a {@code method} tag, so the set of tags does not include a method + * tag. + *

+ * Each tag in the set is represented by an enum which implements {@link AttributeValue}. Each enum has one entry for + * each valid value for that tag. + *

+ * For example, the metric {@code ft.bulkhead.calls.total} has one tag, {@code bulkheadResult} which can have the value + * {@code accepted} or {@code rejected}. The value for this is {@link #BULKHEAD_CALLS} and calling + * {@code BULKHEAD_CALLS.getArgumentClasses()} returns {@link BulkheadResult}, which is an enum with two entries, + * {@link BulkheadResult#ACCEPTED} and {@link BulkheadResult#REJECTED}. + */ +public enum TelemetryMetricDefinition { + INVOCATIONS("ft.invocations.total", MetricType.COUNTER, InvocationResult.class, InvocationFallback.class), + RETRY_CALLS("ft.retry.calls.total", MetricType.COUNTER, RetryRetried.class, RetryResult.class), + RETRY_RETRIES("ft.retry.retries.total", MetricType.COUNTER), + TIMEOUT_CALLS("ft.timeout.calls.total", MetricType.COUNTER, TimeoutTimedOut.class), + TIMEOUT_EXECUTION_DURATION("ft.timeout.executionDuration", MetricType.HISTOGRAM, "nanoseconds"), + CIRCUITBREAKER_CALLS("ft.circuitbreaker.calls.total", MetricType.COUNTER, CircuitBreakerResult.class), + CIRCUITBREAKER_STATE("ft.circuitbreaker.state.total", MetricType.COUNTER, "nanoseconds", + CircuitBreakerState.class), + CIRCUITBREAKER_OPENED("ft.circuitbreaker.opened.total", MetricType.COUNTER), + BULKHEAD_CALLS("ft.bulkhead.calls.total", MetricType.COUNTER, BulkheadResult.class), + BULKHEAD_EXECUTIONS_RUNNING("ft.bulkhead.executionsRunning", MetricType.GAUGE), + BULKHEAD_EXECUTIONS_WAITING("ft.bulkhead.executionsWaiting", MetricType.GAUGE), + BULKHEAD_RUNNING_DURATION("ft.bulkhead.runningDuration", MetricType.HISTOGRAM, "nanoseconds"), + BULKHEAD_WAITING_DURATION("ft.bulkhead.waitingDuration", MetricType.HISTOGRAM, "nanoseconds"); + + public enum MetricType { + COUNTER, + GAUGE, + HISTOGRAM + } + + private String name; + private String unit; + private MetricType metricType; + private Class[] getAttributeClasses; + + @SafeVarargs + private TelemetryMetricDefinition(String name, MetricType metricType, String unit, + Class... tagClasses) { + this.name = name; + this.unit = unit; + this.metricType = metricType; + this.getAttributeClasses = tagClasses; + } + + @SafeVarargs + private TelemetryMetricDefinition(String name, MetricType metricType, + Class... tagClasses) { + this(name, metricType, null, tagClasses); + } + + /** + * The metric name + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * The metric unit + * + * @return the unit + */ + public String getUnit() { + return unit; + } + + /** + * The subclass of {@link Metric} used by this metric + * + * @return the metric class + */ + public MetricType getMetricType() { + return metricType; + } + + /** + * The tags which are applied to this metric + *

+ * The classes returned from this method will be enums which implement {@link AttributeValue} + * + * @return the tags which are applied to this metric + */ + public Class[] getAttributeClasses() { + return getAttributeClasses; + } + + public interface AttributeValue { + public Attributes getAttribute(); + } + + public enum BulkheadResult implements AttributeValue { + ACCEPTED("accepted"), REJECTED("rejected"); + + private Attributes attributes; + + private BulkheadResult(String attributeValue) { + AttributeKey key = AttributeKey.stringKey("bulkheadResult"); + attributes = Attributes.builder().put(key, attributeValue).build(); + } + + public Attributes getAttribute() { + return attributes; + } + } + + public enum CircuitBreakerResult implements AttributeValue { + SUCCESS("success"), FAILURE("failure"), CIRCUIT_BREAKER_OPEN("circuitBreakerOpen"); + + private Attributes attribute; + + private CircuitBreakerResult(String attributeValue) { + AttributeKey key = AttributeKey.stringKey("circuitBreakerResult"); + attribute = Attributes.builder().put(key, attributeValue).build(); + } + + public Attributes getAttribute() { + return attribute; + } + } + + public enum CircuitBreakerState implements AttributeValue { + OPEN("open"), CLOSED("closed"), HALF_OPEN("halfOpen"); + + private Attributes attribute; + + private CircuitBreakerState(String attributeValue) { + AttributeKey key = AttributeKey.stringKey("state"); + attribute = Attributes.builder().put(key, attributeValue).build(); + } + + public Attributes getAttribute() { + return attribute; + } + } + + public enum InvocationFallback implements AttributeValue { + APPLIED("applied"), NOT_APPLIED("notApplied"), NOT_DEFINED("notDefined"); + + private Attributes attribute; + + private InvocationFallback(String attributeValue) { + AttributeKey key = AttributeKey.stringKey("fallback"); + attribute = Attributes.builder().put(key, attributeValue).build(); + } + + public Attributes getAttribute() { + return attribute; + } + } + + public enum InvocationResult implements AttributeValue { + VALUE_RETURNED("valueReturned"), EXCEPTION_THROWN("exceptionThrown"); + + private Attributes attribute; + + private InvocationResult(String attributeValue) { + AttributeKey key = AttributeKey.stringKey("result"); + attribute = Attributes.builder().put(key, attributeValue).build(); + } + + public Attributes getAttribute() { + return attribute; + } + } + + public enum RetryResult implements AttributeValue { + VALUE_RETURNED("valueReturned"), EXCEPTION_NOT_RETRYABLE("exceptionNotRetryable"), MAX_RETRIES_REACHED( + "maxRetriesReached"), + MAX_DURATION_REACHED("maxDurationReached"); + + private Attributes attribute; + + private RetryResult(String attributeValue) { + AttributeKey key = AttributeKey.stringKey("retryResult"); + attribute = Attributes.builder().put(key, attributeValue).build(); + } + + public Attributes getAttribute() { + return attribute; + } + } + + public enum RetryRetried implements AttributeValue { + TRUE("true"), FALSE("false"); + + private Attributes attribute; + + private RetryRetried(String attributeValue) { + AttributeKey key = AttributeKey.stringKey("retried"); + attribute = Attributes.builder().put(key, attributeValue).build(); + } + + public Attributes getAttribute() { + return attribute; + } + } + + public enum TimeoutTimedOut implements AttributeValue { + TRUE("true"), FALSE("false"); + + private Attributes attribute; + + private TimeoutTimedOut(String attributeValue) { + AttributeKey key = AttributeKey.stringKey("timedOut"); + attribute = Attributes.builder().put(key, attributeValue).build(); + } + + public Attributes getAttribute() { + return attribute; + } + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryMetricGetter.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryMetricGetter.java new file mode 100644 index 00000000..fc2d0149 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryMetricGetter.java @@ -0,0 +1,282 @@ +/* + ******************************************************************************* + * 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; + +import static org.testng.Assert.fail; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.AttributeValue; +import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.BulkheadResult; +import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerResult; +import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.CircuitBreakerState; +import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationFallback; +import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult; +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 io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; + +/** + * Retrieves metrics for a specific method + *

+ * The MetricGetter facilitates testing of metrics by providing helper methods for retrieving metrics defined by the + * faulttolerance spec for a specific class and method name. + *

+ * This class will never create a metric which does not already exist. + */ +public class TelemetryMetricGetter { + + private final String classMethodName; + + private Map counterMetrics = new HashMap<>(); + private Map gaugeMetrics = new HashMap<>(); + private Map histogramMetrics = new HashMap<>(); + + public TelemetryMetricGetter(Class clazz, String methodName) { + validateClassAndMethodName(clazz, methodName); + classMethodName = clazz.getCanonicalName() + "." + methodName; + } + + public TelemetryCounterMetric getInvocations(InvocationResult result, InvocationFallback fallbackUsed) { + return getCounterMetric(getMetricId(TelemetryMetricDefinition.INVOCATIONS, result, fallbackUsed)); + } + + public TelemetryCounterMetric getRetryCalls(RetryRetried retried, RetryResult result) { + return getCounterMetric(getMetricId(TelemetryMetricDefinition.RETRY_CALLS, retried, result)); + } + + public TelemetryCounterMetric getRetryRetries() { + return getCounterMetric(getMetricId(TelemetryMetricDefinition.RETRY_RETRIES)); + } + + public TelemetryCounterMetric getTimeoutCalls(TimeoutTimedOut timedOut) { + return getCounterMetric(getMetricId(TelemetryMetricDefinition.TIMEOUT_CALLS, timedOut)); + } + + public TelemetryHistogramMetric getTimeoutExecutionDuration() { + return getHistogramMetric(getMetricId(TelemetryMetricDefinition.TIMEOUT_EXECUTION_DURATION)); + } + + public TelemetryCounterMetric getCircuitBreakerCalls(CircuitBreakerResult cbResult) { + return getCounterMetric(getMetricId(TelemetryMetricDefinition.CIRCUITBREAKER_CALLS, cbResult)); + } + + public TelemetryCounterMetric getCircuitBreakerOpened() { + return getCounterMetric(getMetricId(TelemetryMetricDefinition.CIRCUITBREAKER_OPENED)); + } + + public TelemetryCounterMetric getCircuitBreakerState(CircuitBreakerState cbState) { + return getCounterMetric(getMetricId(TelemetryMetricDefinition.CIRCUITBREAKER_STATE, cbState)); + } + + public TelemetryCounterMetric getBulkheadCalls(BulkheadResult bulkheadResult) { + return getCounterMetric(getMetricId(TelemetryMetricDefinition.BULKHEAD_CALLS, bulkheadResult)); + } + + public TelemetryGaugeMetric getBulkheadExecutionsRunning() { + return getGaugeMetric(getMetricId(TelemetryMetricDefinition.BULKHEAD_EXECUTIONS_RUNNING)); + } + + public TelemetryGaugeMetric getBulkheadExecutionsWaiting() { + return getGaugeMetric(getMetricId(TelemetryMetricDefinition.BULKHEAD_EXECUTIONS_WAITING)); + } + + public TelemetryHistogramMetric getBulkheadRunningDuration() { + return getHistogramMetric(getMetricId(TelemetryMetricDefinition.BULKHEAD_RUNNING_DURATION)); + } + + public TelemetryHistogramMetric getBulkheadWaitingDuration() { + return getHistogramMetric(getMetricId(TelemetryMetricDefinition.BULKHEAD_WAITING_DURATION)); + } + + /** + * Calls {@code baseline()} on all relevant metrics. + *

+ * Extracts all of the {@code Counter} and {@code Gauge} metrics from {@link TelemetryMetricDefinition} and calls + * {@link TelemetryCounterMetric#baseline()} or {@link TelemetryGaugeMetric#baseline()} on them. + *

+ * This allows us to check how they've changed later in the test using the {@code CounterMetric.delta()} or + * {@code GaugeMetric.delta()} methods, without having to explicitly baseline every metric ourselves up front. + */ + public void baselineMetrics() { + for (TelemetryMetricDefinition definition : TelemetryMetricDefinition.values()) { + for (AttributeValue[] tags : getTagCombinations(definition.getAttributeClasses())) { + TelemetryMetricID id = getMetricId(definition, tags); + if (definition.getMetricType() == TelemetryMetricDefinition.MetricType.COUNTER) { + getCounterMetric(id).baseline(); + } + if (definition.getMetricType() == TelemetryMetricDefinition.MetricType.GAUGE) { + getGaugeMetric(id).baseline(); + } + } + } + } + + // ----------------------- + // Private methods + // ----------------------- + + /** + * Computes all possible values for a set of tags. + *

+ * Given an array of TagValue enums, this method find every combination of values from across this set of tags. + *

+ * For example, if we had two tags {@code foo=[a|b]} and {@code bar=[x|y]}, this method would return + * + *

+     * [[foo=a, bar=x],
+     *  [foo=a, bar=y],
+     *  [foo=b, bar=x],
+     *  [foo=b, bar=y]]
+     * 
+ *

+ * We can use this to iterate across all of the {@link TelemetryMetricID}s which could be created for a metric which + * has multiple tags. + *

+ * If called with no arguments, this method returns an array containing an empty array (indicating the only possible + * combination the the one with no tag values at all). + * + * @param tagValueClazzes + * the set of tags + * @return every possible combination when taking one value for each of the given tags + */ + @SafeVarargs + public static final AttributeValue[][] getTagCombinations(Class... tagValueClazzes) { + int combinations = 1; + for (Class clazz : tagValueClazzes) { + combinations *= clazz.getEnumConstants().length; + } + + AttributeValue[][] result = new AttributeValue[combinations][]; + List> tagLists = getTagCombinations(Arrays.asList(tagValueClazzes)); + for (int i = 0; i < tagLists.size(); i++) { + List tagList = tagLists.get(i); + result[i] = tagList.toArray(new AttributeValue[tagList.size()]); + } + return result; + } + + private static List> getTagCombinations( + List> tagValueClazzes) { + if (tagValueClazzes.isEmpty()) { + return Collections.singletonList(Collections.emptyList()); + } + + List> result = new ArrayList<>(); + Class firstClazz = tagValueClazzes.get(0); + for (AttributeValue value : firstClazz.getEnumConstants()) { + for (List tagList : getTagCombinations( + tagValueClazzes.subList(1, tagValueClazzes.size()))) { + ArrayList newList = new ArrayList<>(); + newList.add(value); + newList.addAll(tagList); + result.add(newList); + } + } + return result; + } + + /** + * Creates a {@link TelemetryMetricID} for a {@link TelemetryMetricDefinition} and a set of tag values. + *

+ * This method will check that the {@code TagValue}s passed in match the value of + * {@link TelemetryMetricDefinition#getTagClasses()}. + * + * @param metricDefinition + * the definition of the metric + * @param metricTags + * the values for the tags of the metric + * @return the MetricID for {@code metricDefinition} with the tags in {@code metricTags} + */ + private TelemetryMetricID getMetricId(TelemetryMetricDefinition metricDefinition, + AttributeValue... metricAttributes) { + if (metricDefinition.getAttributeClasses().length != metricAttributes.length) { + throw new IllegalArgumentException("Wrong number of arguments passed for " + metricDefinition); + } + + AttributesBuilder builder = Attributes.builder(); + + for (int i = 0; i < metricAttributes.length; i++) { + Class argClazz = metricDefinition.getAttributeClasses()[i]; + if (!argClazz.isInstance(metricAttributes[i])) { + throw new IllegalArgumentException("Argument " + i + " has the wrong type. " + + "Was " + metricAttributes[i].getClass() + " but expected " + argClazz); + } + + builder.putAll(metricAttributes[i].getAttribute()); + } + + builder.put("method", classMethodName); + + return new TelemetryMetricID(metricDefinition.getName(), builder.build()); + } + + /** + * Get or create the {@link TelemetryCounterMetric} for the given {@link TelemetryMetricID} + *

+ * Each created {@code CounterMetric} will be stored and calling this method twice with the same {@code MetricID} + * will return the same {@code CounterMetric}. + * + * @param metricId + * the {@code MetricID} + * @return the {@code CounterMetric} for {@code metricId} + */ + private TelemetryCounterMetric getCounterMetric(TelemetryMetricID metricId) { + return counterMetrics.computeIfAbsent(metricId, m -> new TelemetryCounterMetric(m)); + } + + /** + * Get or create the {@link TelemetryGaugeMetric} for the given {@link TelemetryMetricID} + *

+ * Each created {@code GaugeMetric} will be stored and calling this method twice with the same {@code MetricID} will + * return the same {@code GaugeMetric}. + * + * @param metricId + * the {@code MetricID} + * @return the {@code GaugeMetric} for {@code metricId} + */ + private TelemetryGaugeMetric getGaugeMetric(TelemetryMetricID metricId) { + return gaugeMetrics.computeIfAbsent(metricId, m -> new TelemetryGaugeMetric(m)); + } + + private TelemetryHistogramMetric getHistogramMetric(TelemetryMetricID metricId) { + return histogramMetrics.computeIfAbsent(metricId, m -> new TelemetryHistogramMetric(m)); + } + + private void validateClassAndMethodName(Class clazz, String methodName) { + for (Method m : clazz.getDeclaredMethods()) { + if (m.getName().equals(methodName)) { + return; + } + } + + fail("Couldn't find method " + methodName + " on class " + clazz.getCanonicalName()); + } +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryMetricID.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryMetricID.java new file mode 100644 index 00000000..8a396ba0 --- /dev/null +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/TelemetryMetricID.java @@ -0,0 +1,88 @@ +/* + ******************************************************************************* + * 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.Arrays; +import java.util.Map; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; + +public class TelemetryMetricID { + + public final String name; + public final Attributes attributes; + + public TelemetryMetricID(String classMethodName, Attributes attributes) { + this.name = classMethodName; + this.attributes = attributes; + + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(name + " Attributes:"); + for (AttributeKey key : attributes.asMap().keySet()) { + sb.append("[" + key.toString() + "=" + attributes.asMap().get(key).toString() + "]"); + } + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + TelemetryMetricID other = (TelemetryMetricID) o; + + if (name != other.name) { + return false; + } + + if (attributes.size() != other.attributes.size()) { + return false; + } + + for (AttributeKey key : attributes.asMap().keySet()) { + if (attributes.get(key) != other.attributes.get(key)) { + return false; + } + } + + return true; + } + + @Override + public int hashCode() { + Map map = attributes.asMap(); + Object[] names = map.entrySet().toArray(); + Object[] value = map.keySet().toArray(); + + int namesHash = Arrays.deepHashCode(names); + int valuesHash = Arrays.deepHashCode(value); + + int hash = 17; + hash = hash * 31 + namesHash; + hash = hash * 31 + valuesHash; + hash = hash * 31 + name.hashCode(); + + return hash; + } + +} diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/util/Exceptions.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/util/Exceptions.java index 75aace49..3ee24e7a 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/util/Exceptions.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/util/Exceptions.java @@ -125,7 +125,9 @@ public static void expect(Class expectedException, Exceptio fail("Expected exception not thrown. Expected " + expectedException.getName()); } catch (Exception e) { if (!expectedException.isInstance(e)) { - fail("Unexpected exception thrown", e); + fail("Unexpected exception thrown. Expected " + expectedException.getName() + ":" + + expectedException.getClassLoader().toString() + " but got: " + + e.getClass().getName() + ":" + e.getClass().getClassLoader().toString(), e); } } } diff --git a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/util/Packages.java b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/util/Packages.java index 1da22444..fced1536 100644 --- a/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/util/Packages.java +++ b/tck/src/main/java/org/eclipse/microprofile/fault/tolerance/tck/util/Packages.java @@ -36,5 +36,11 @@ private Packages() { * The {@code org.eclipse.microprofile.fault.tolerance.tck.metrics.util} package */ public static final Package METRIC_UTILS = - org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricComparator.class.getPackage(); + org.eclipse.microprofile.fault.tolerance.tck.metrics.util.MetricGetter.class.getPackage(); + + /** + * The {@code org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util} package + */ + public static final Package TELEMETRY_METRIC_UTILS = + org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricGetter.class.getPackage(); } diff --git a/tck/src/test/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/MetricGetterTest.java b/tck/src/test/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/MetricGetterTest.java new file mode 100644 index 00000000..dcf1dfbf --- /dev/null +++ b/tck/src/test/java/org/eclipse/microprofile/fault/tolerance/tck/telemetryMetrics/util/MetricGetterTest.java @@ -0,0 +1,56 @@ +/* + ******************************************************************************* + * 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; + +import static org.testng.Assert.assertEquals; + +import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.AttributeValue; +import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.InvocationResult; +import org.eclipse.microprofile.fault.tolerance.tck.telemetryMetrics.util.TelemetryMetricDefinition.TimeoutTimedOut; +import org.testng.annotations.Test; + +/** + * Tests for MetricGetterTest.getTagCombinations() + */ +public class MetricGetterTest { + + @Test + public void testTagComboZero() { + AttributeValue[][] expected = {{}}; + assertEquals(TelemetryMetricGetter.getTagCombinations(), expected); + } + + @Test + public void testTagComboOne() { + AttributeValue[][] expected = {{TimeoutTimedOut.TRUE}, {TimeoutTimedOut.FALSE}}; + assertEquals(TelemetryMetricGetter.getTagCombinations(TimeoutTimedOut.class), expected); + } + + @Test + public void testTagComboTwo() { + AttributeValue[][] expected = {{TimeoutTimedOut.TRUE, InvocationResult.VALUE_RETURNED}, + {TimeoutTimedOut.TRUE, InvocationResult.EXCEPTION_THROWN}, + {TimeoutTimedOut.FALSE, InvocationResult.VALUE_RETURNED}, + {TimeoutTimedOut.FALSE, InvocationResult.EXCEPTION_THROWN}}; + assertEquals(TelemetryMetricGetter.getTagCombinations(TimeoutTimedOut.class, InvocationResult.class), expected); + } + +}