From dd7f3c16c8abd8fd1bbcea497bfd2d0318875188 Mon Sep 17 00:00:00 2001 From: Auri Munoz Date: Thu, 14 Sep 2023 15:39:48 +0200 Subject: [PATCH] Stork observability integration --- bom/application/pom.xml | 2 +- extensions/micrometer/deployment/pom.xml | 22 ++- .../binder/StorkBinderProcessor.java | 30 ++++ .../binder/StorkMetricsDisabledTest.java | 33 +++++ .../StorkMetricsLoadBalancerFailTest.java | 99 ++++++++++++++ .../StorkMetricsServiceDiscoveryFailTest.java | 98 ++++++++++++++ .../deployment/binder/StorkMetricsTest.java | 85 ++++++++++++ .../MockServiceDiscoveryConfiguration.java | 50 +++++++ .../test/MockServiceDiscoveryProvider.java | 30 ++++ .../MockServiceDiscoveryProviderLoader.java | 44 ++++++ .../MockServiceSelectorConfiguration.java | 50 +++++++ .../test/MockServiceSelectorProvider.java | 26 ++++ .../MockServiceSelectorProviderLoader.java | 42 ++++++ .../java/io/quarkus/micrometer/test/Util.java | 11 ++ extensions/micrometer/runtime/pom.xml | 12 ++ .../stork/StorkObservationCollectorBean.java | 128 ++++++++++++++++++ .../runtime/config/MicrometerConfig.java | 1 + .../runtime/config/StorkConfigGroup.java | 33 +++++ .../QuarkusStorkObservableInfrastructure.java | 22 +++ .../quarkus/stork/SmallRyeStorkRecorder.java | 13 +- .../resteasy-reactive/pom.xml | 2 +- .../rest-client-reactive-stork/pom.xml | 27 ++++ .../stork/RestClientReactiveStorkTest.java | 48 +++++++ 23 files changed, 901 insertions(+), 7 deletions(-) create mode 100644 extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/StorkBinderProcessor.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsDisabledTest.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsLoadBalancerFailTest.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsServiceDiscoveryFailTest.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsTest.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryConfiguration.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProvider.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProviderLoader.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorConfiguration.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorProvider.java create mode 100644 extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorProviderLoader.java create mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/stork/StorkObservationCollectorBean.java create mode 100644 extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/StorkConfigGroup.java create mode 100644 extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/QuarkusStorkObservableInfrastructure.java diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 83a4d773d4754..d4c3608ca16f8 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -64,7 +64,7 @@ 3.0.1 3.6.0 4.9.0 - 2.3.1 + 2.4.0-SNAPSHOT 2.1.2 2.1.1 3.0.0 diff --git a/extensions/micrometer/deployment/pom.xml b/extensions/micrometer/deployment/pom.xml index 8273cf3a321fe..c2f3a96cd625e 100644 --- a/extensions/micrometer/deployment/pom.xml +++ b/extensions/micrometer/deployment/pom.xml @@ -81,22 +81,26 @@ io.quarkus - quarkus-resteasy-deployment + quarkus-resteasy-reactive-deployment test io.quarkus - quarkus-rest-client-deployment + quarkus-rest-client-reactive-deployment test io.quarkus - quarkus-resteasy-jackson-deployment + quarkus-resteasy-reactive-jackson-deployment + test + + + io.smallrye.stork + stork-service-discovery-static-list test - io.quarkus quarkus-undertow-deployment @@ -132,6 +136,16 @@ resteasy-reactive-client test + + org.assertj + assertj-core + test + + + org.mockito + mockito-core + test + diff --git a/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/StorkBinderProcessor.java b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/StorkBinderProcessor.java new file mode 100644 index 0000000000000..06ed79c3ea715 --- /dev/null +++ b/extensions/micrometer/deployment/src/main/java/io/quarkus/micrometer/deployment/binder/StorkBinderProcessor.java @@ -0,0 +1,30 @@ +package io.quarkus.micrometer.deployment.binder; + +import java.util.function.BooleanSupplier; + +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.micrometer.runtime.MicrometerRecorder; +import io.quarkus.micrometer.runtime.config.MicrometerConfig; + +public class StorkBinderProcessor { + + static final String OBSERVABLE_CLIENT = "io.smallrye.stork.api.Service"; + static final String METRICS_BEAN_CLASS = "io.quarkus.micrometer.runtime.binder.stork.StorkObservationCollectorBean"; + + static final Class OBSERVABLE_CLIENT_CLASS = MicrometerRecorder.getClassForName(OBSERVABLE_CLIENT); + + static class StorkMetricsSupportEnabled implements BooleanSupplier { + MicrometerConfig mConfig; + + public boolean getAsBoolean() { + return OBSERVABLE_CLIENT_CLASS != null && mConfig.checkBinderEnabledWithDefault(mConfig.binder.stork); + } + } + + @BuildStep(onlyIf = StorkMetricsSupportEnabled.class) + AdditionalBeanBuildItem addStorkObservationCollector() { + return AdditionalBeanBuildItem.unremovableOf(METRICS_BEAN_CLASS); + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsDisabledTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsDisabledTest.java new file mode 100644 index 0000000000000..9d68109900f45 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsDisabledTest.java @@ -0,0 +1,33 @@ +package io.quarkus.micrometer.deployment.binder; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.stork.api.observability.ObservationCollector; + +public class StorkMetricsDisabledTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder.stork.enabled", "false") + .overrideConfigKey("quarkus.micrometer.binder-enabled-default", "false") + .overrideConfigKey("quarkus.micrometer.registry-enabled-default", "false") + .withEmptyApplication(); + + @Inject + Instance bean; + + @Test + void testNoInstancePresentIfNoRedisClientsClass() { + assertTrue(bean.isUnsatisfied(), + "No redis metrics bean"); + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsLoadBalancerFailTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsLoadBalancerFailTest.java new file mode 100644 index 0000000000000..a817f8da19ebb --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsLoadBalancerFailTest.java @@ -0,0 +1,99 @@ + +package io.quarkus.micrometer.deployment.binder; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.concurrent.TimeUnit; + +import jakarta.inject.Inject; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mockito; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; +import io.quarkus.micrometer.runtime.binder.stork.StorkObservationCollectorBean; +import io.quarkus.micrometer.test.MockServiceSelectorConfiguration; +import io.quarkus.micrometer.test.MockServiceSelectorProvider; +import io.quarkus.micrometer.test.MockServiceSelectorProviderLoader; +import io.quarkus.micrometer.test.PingPongResource; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.smallrye.stork.api.observability.ObservationPoints; + +@DisabledOnOs(OS.WINDOWS) +public class StorkMetricsLoadBalancerFailTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder.stork.enabled", "true") + .overrideConfigKey("quarkus.stork.pingpong-service.service-discovery.type", "static") + .overrideConfigKey("quarkus.stork.pingpong-service.service-discovery.address-list", "${test.url}") + .overrideConfigKey("pingpong/mp-rest/url", "stork://pingpong-service") + .overrideConfigKey("quarkus.stork.pingpong-service.load-balancer.type", "mock") + .withApplicationRoot((jar) -> jar + .addClasses(PingPongResource.class, PingPongResource.PingPongRestClient.class, + MockServiceSelectorProvider.class, MockServiceSelectorConfiguration.class, + MockServiceSelectorProviderLoader.class, Util.class)); + + @Inject + MeterRegistry registry; + + @Inject + MockServiceSelectorProvider provider; + + @Test + public void shouldGetStorkMetricsWhenServiceSelectorFails() { + + Mockito.when(provider.getLoadBalancer().selectServiceInstance(Mockito.anyCollection())) + .thenThrow(new RuntimeException("Load Balancer induced failure")); + RestAssured.when().get("/ping/one").then().statusCode(500); + + //Stork metrics + assertStorkMetrics(); + + // Stork metrics exposed to Micrometer + Counter instanceCounter = registry.get("stork.instances.count").counter(); + Timer serviceDiscoveryDuration = registry.get("stork.service-discovery.duration").timer(); + Timer serviceSelectionDuration = registry.get("stork.service-selection.duration").timer(); + Timer overallDuration = registry.get("stork.overall.duration").timer(); + Gauge serviceDiscoveryFailures = registry.get("stork.service-discovery.failures").gauge(); + Gauge loadBalancerFailures = registry.get("stork.load-balancer.failures").gauge(); + + Util.assertTags(Tag.of("service-name", "pingpong-service"), instanceCounter, serviceDiscoveryDuration, + serviceSelectionDuration, overallDuration); + + assertThat(StorkObservationCollectorBean.STORK_METRICS.failure()).isNotNull(); + assertThat(instanceCounter.count()).isEqualTo(1); + assertThat(serviceDiscoveryFailures.value()).isEqualTo(0); + assertThat(loadBalancerFailures.value()).isEqualTo(1); + assertThat(serviceDiscoveryDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + assertThat(serviceSelectionDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + assertThat(overallDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + + } + + private static void assertStorkMetrics() { + ObservationPoints.StorkResolutionEvent metrics = StorkObservationCollectorBean.STORK_METRICS; + Assertions.assertThat(metrics.getDiscoveredInstancesCount()).isEqualTo(1); + Assertions.assertThat(metrics.getSelectedInstanceId()).isEqualTo(-1); + Assertions.assertThat(metrics.getServiceName()).isEqualTo("pingpong-service"); + Assertions.assertThat(metrics.isDone()).isTrue(); + Assertions.assertThat(metrics.failure().getMessage()).isEqualTo("Load Balancer induced failure"); + Assertions.assertThat(metrics.getOverallDuration()).isNotNull(); + Assertions.assertThat(metrics.getServiceDiscoveryType()).isEqualTo("static"); + Assertions.assertThat(metrics.getServiceSelectionType()).isEqualTo("mock"); + Assertions.assertThat(metrics.getServiceDiscoveryDuration()).isNotNull(); + Assertions.assertThat(metrics.getServiceSelectionDuration()).isNotNull(); + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsServiceDiscoveryFailTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsServiceDiscoveryFailTest.java new file mode 100644 index 0000000000000..acca8469b044d --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsServiceDiscoveryFailTest.java @@ -0,0 +1,98 @@ + +package io.quarkus.micrometer.deployment.binder; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.concurrent.TimeUnit; + +import jakarta.inject.Inject; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mockito; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; +import io.quarkus.micrometer.runtime.binder.stork.StorkObservationCollectorBean; +import io.quarkus.micrometer.test.MockServiceDiscoveryConfiguration; +import io.quarkus.micrometer.test.MockServiceDiscoveryProvider; +import io.quarkus.micrometer.test.MockServiceDiscoveryProviderLoader; +import io.quarkus.micrometer.test.PingPongResource; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.RestAssured; +import io.smallrye.mutiny.Uni; +import io.smallrye.stork.api.observability.ObservationPoints; + +@DisabledOnOs(OS.WINDOWS) +public class StorkMetricsServiceDiscoveryFailTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder.stork.enabled", "true") + .overrideConfigKey("quarkus.stork.pingpong-service.service-discovery.type", "mock") + .overrideConfigKey("pingpong/mp-rest/url", "stork://pingpong-service") + .withApplicationRoot((jar) -> jar + .addClasses(PingPongResource.class, PingPongResource.PingPongRestClient.class, + MockServiceDiscoveryProvider.class, MockServiceDiscoveryConfiguration.class, + MockServiceDiscoveryProviderLoader.class, Util.class)); + + @Inject + MeterRegistry registry; + + @Inject + MockServiceDiscoveryProvider provider; + + @Test + public void shouldGetStorkMetricsWhenServiceDiscoveryFails() { + + Mockito.when(provider.getServiceDiscovery().getServiceInstances()) + .thenReturn(Uni.createFrom().failure(new RuntimeException("Service Discovery induced failure"))); + RestAssured.when().get("/ping/one").then().statusCode(500); + + //Stork metrics + assertStorkMetrics(); + + // Stork metrics exposed to Micrometer + Counter instanceCounter = registry.get("stork.instances.count").counter(); + Timer serviceDiscoveryDuration = registry.get("stork.service-discovery.duration").timer(); + Timer serviceSelectionDuration = registry.get("stork.service-selection.duration").timer(); + Timer overallDuration = registry.get("stork.overall.duration").timer(); + Gauge serviceDiscoveryFailures = registry.get("stork.service-discovery.failures").gauge(); + Gauge loadBalancerFailures = registry.get("stork.load-balancer.failures").gauge(); + + Util.assertTags(Tag.of("service-name", "pingpong-service"), instanceCounter, serviceDiscoveryDuration, + serviceSelectionDuration, overallDuration); + + assertThat(StorkObservationCollectorBean.STORK_METRICS.failure()).isNotNull(); + assertThat(instanceCounter.count()).isEqualTo(0); + assertThat(serviceDiscoveryFailures.value()).isEqualTo(1); + assertThat(loadBalancerFailures.value()).isEqualTo(0); + assertThat(serviceDiscoveryDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + assertThat(serviceSelectionDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + assertThat(overallDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + + } + + private static void assertStorkMetrics() { + ObservationPoints.StorkResolutionEvent metrics = StorkObservationCollectorBean.STORK_METRICS; + Assertions.assertThat(metrics.getDiscoveredInstancesCount()).isEqualTo(0); + Assertions.assertThat(metrics.getSelectedInstanceId()).isEqualTo(-1); + Assertions.assertThat(metrics.getServiceName()).isEqualTo("pingpong-service"); + Assertions.assertThat(metrics.isDone()).isTrue(); + Assertions.assertThat(metrics.failure().getMessage()).isEqualTo("Service Discovery induced failure"); + Assertions.assertThat(metrics.getOverallDuration()).isNotNull(); + Assertions.assertThat(metrics.getServiceDiscoveryType()).isEqualTo("mock"); + Assertions.assertThat(metrics.getServiceSelectionType()).isEqualTo("round-robin"); + Assertions.assertThat(metrics.getServiceDiscoveryDuration()).isNotNull(); + Assertions.assertThat(metrics.getServiceSelectionDuration()).isNotNull(); + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsTest.java new file mode 100644 index 0000000000000..a546961636422 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsTest.java @@ -0,0 +1,85 @@ + +package io.quarkus.micrometer.deployment.binder; + +import static io.restassured.RestAssured.when; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.concurrent.TimeUnit; + +import jakarta.inject.Inject; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; +import io.quarkus.micrometer.runtime.binder.stork.StorkObservationCollectorBean; +import io.quarkus.micrometer.test.PingPongResource; +import io.quarkus.micrometer.test.Util; +import io.quarkus.test.QuarkusUnitTest; +import io.smallrye.stork.api.observability.ObservationPoints; + +@DisabledOnOs(OS.WINDOWS) +public class StorkMetricsTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .withConfigurationResource("test-logging.properties") + .overrideConfigKey("quarkus.micrometer.binder.stork.enabled", "true") + .overrideConfigKey("quarkus.stork.pingpong-service.service-discovery.type", "static") + .overrideConfigKey("quarkus.stork.pingpong-service.service-discovery.address-list", "${test.url}") + .overrideConfigKey("pingpong/mp-rest/url", "stork://pingpong-service") + .withApplicationRoot((jar) -> jar + .addClasses(PingPongResource.class, PingPongResource.PingPongRestClient.class, Util.class)); + + @Inject + MeterRegistry registry; + + @Test + public void shouldGetStorkMetricsWhenEverythingSucceded() { + when().get("/ping/one").then().statusCode(200); + + //Stork metrics + assertStorkMetrics(); + + // Stork metrics exposed to Micrometer + Counter instanceCounter = registry.get("stork.instances.count").counter(); + Timer serviceDiscoveryDuration = registry.get("stork.service-discovery.duration").timer(); + Timer serviceSelectionDuration = registry.get("stork.service-selection.duration").timer(); + Timer overallDuration = registry.get("stork.overall.duration").timer(); + Gauge serviceDiscoveryFailures = registry.get("stork.service-discovery.failures").gauge(); + Gauge loadBalancerFailures = registry.get("stork.load-balancer.failures").gauge(); + + Util.assertTags(Tag.of("service-name", "pingpong-service"), instanceCounter, serviceDiscoveryDuration, + serviceSelectionDuration, overallDuration); + + Assertions.assertThat(instanceCounter.count()).isEqualTo(1); + Assertions.assertThat(serviceDiscoveryFailures.value()).isEqualTo(0); + Assertions.assertThat(loadBalancerFailures.value()).isEqualTo(0); + Assertions.assertThat(serviceDiscoveryDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + Assertions.assertThat(serviceSelectionDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + Assertions.assertThat(overallDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + + } + + private static void assertStorkMetrics() { + ObservationPoints.StorkResolutionEvent metrics = StorkObservationCollectorBean.STORK_METRICS; + Assertions.assertThat(metrics.getDiscoveredInstancesCount()).isEqualTo(1); + Assertions.assertThat(metrics.getSelectedInstanceId()).isEqualTo(0); + Assertions.assertThat(metrics.getServiceName()).isEqualTo("pingpong-service"); + Assertions.assertThat(metrics.isDone()).isTrue(); + Assertions.assertThat(metrics.failure()).isNull(); + Assertions.assertThat(metrics.getOverallDuration()).isNotNull(); + Assertions.assertThat(metrics.getServiceDiscoveryType()).isEqualTo("static"); + Assertions.assertThat(metrics.getServiceSelectionType()).isEqualTo("round-robin"); + Assertions.assertThat(metrics.getServiceDiscoveryDuration()).isNotNull(); + Assertions.assertThat(metrics.getServiceSelectionDuration()).isNotNull(); + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryConfiguration.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryConfiguration.java new file mode 100644 index 0000000000000..ed77088d030a7 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryConfiguration.java @@ -0,0 +1,50 @@ +package io.quarkus.micrometer.test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Configuration for the {@code MockServiceDiscoveryProvider} ServiceDiscovery. + */ +public class MockServiceDiscoveryConfiguration implements io.smallrye.stork.api.config.ConfigWithType { + private final Map parameters; + + /** + * Creates a new MockConfiguration + * + * @param params the parameters, must not be {@code null} + */ + public MockServiceDiscoveryConfiguration(Map params) { + parameters = Collections.unmodifiableMap(params); + } + + /** + * Creates a new MockConfiguration + */ + public MockServiceDiscoveryConfiguration() { + parameters = Collections.emptyMap(); + } + + /** + * @return the type + */ + @Override + public String type() { + return "mock"; + } + + /** + * @return the parameters + */ + @Override + public Map parameters() { + return parameters; + } + + private MockServiceDiscoveryConfiguration extend(String key, String value) { + Map copy = new HashMap<>(parameters); + copy.put(key, value); + return new MockServiceDiscoveryConfiguration(copy); + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProvider.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProvider.java new file mode 100644 index 0000000000000..3de40aa6577cf --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProvider.java @@ -0,0 +1,30 @@ +package io.quarkus.micrometer.test; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.mockito.Mockito; + +import io.smallrye.stork.api.ServiceDiscovery; +import io.smallrye.stork.api.config.ServiceConfig; +import io.smallrye.stork.api.config.ServiceDiscoveryType; +import io.smallrye.stork.spi.ServiceDiscoveryProvider; +import io.smallrye.stork.spi.StorkInfrastructure; + +@ServiceDiscoveryType("mock") +@ApplicationScoped +public class MockServiceDiscoveryProvider implements ServiceDiscoveryProvider { + + public ServiceDiscovery getServiceDiscovery() { + return serviceDiscovery; + } + + private final ServiceDiscovery serviceDiscovery = Mockito.mock(ServiceDiscovery.class);; + + @Override + public ServiceDiscovery createServiceDiscovery(MockServiceDiscoveryConfiguration config, String serviceName, + ServiceConfig serviceConfig, + StorkInfrastructure storkInfrastructure) { + return serviceDiscovery; + } + +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProviderLoader.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProviderLoader.java new file mode 100644 index 0000000000000..f68fdd0813cfc --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceDiscoveryProviderLoader.java @@ -0,0 +1,44 @@ +package io.quarkus.micrometer.test; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.CDI; + +import io.smallrye.stork.api.ServiceDiscovery; +import io.smallrye.stork.api.config.ConfigWithType; +import io.smallrye.stork.api.config.ServiceConfig; +import io.smallrye.stork.spi.StorkInfrastructure; + +/** + * ServiceDiscoveryLoader for {@link io.quarkus.micrometer.test.MockServiceDiscoveryProvider} + */ +@ApplicationScoped +public class MockServiceDiscoveryProviderLoader implements io.smallrye.stork.spi.internal.ServiceDiscoveryLoader { + private final io.quarkus.micrometer.test.MockServiceDiscoveryProvider provider; + + public MockServiceDiscoveryProviderLoader() { + io.quarkus.micrometer.test.MockServiceDiscoveryProvider actual = null; + try { + actual = CDI.current().select(io.quarkus.micrometer.test.MockServiceDiscoveryProvider.class).get(); + } catch (Exception e) { + // Use direct instantiation + actual = new io.quarkus.micrometer.test.MockServiceDiscoveryProvider(); + } + this.provider = actual; + } + + @Override + public ServiceDiscovery createServiceDiscovery(ConfigWithType config, String serviceName, + ServiceConfig serviceConfig, StorkInfrastructure storkInfrastructure) { + MockServiceDiscoveryConfiguration typedConfig = new MockServiceDiscoveryConfiguration( + config.parameters()); + return provider.createServiceDiscovery(typedConfig, serviceName, serviceConfig, storkInfrastructure); + } + + /** + * @return the type + */ + @Override + public String type() { + return "mock"; + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorConfiguration.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorConfiguration.java new file mode 100644 index 0000000000000..5c0fcef7f6b1c --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorConfiguration.java @@ -0,0 +1,50 @@ +package io.quarkus.micrometer.test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Configuration for the {@code MockLoadBalancerProvider} LoadBalancer. + */ +public class MockServiceSelectorConfiguration implements io.smallrye.stork.api.config.ConfigWithType { + private final Map parameters; + + /** + * Creates a new FakeSelectorConfiguration + * + * @param params the parameters, must not be {@code null} + */ + public MockServiceSelectorConfiguration(Map params) { + parameters = Collections.unmodifiableMap(params); + } + + /** + * Creates a new FakeSelectorConfiguration + */ + public MockServiceSelectorConfiguration() { + parameters = Collections.emptyMap(); + } + + /** + * @return the type + */ + @Override + public String type() { + return "fake-selector"; + } + + /** + * @return the parameters + */ + @Override + public Map parameters() { + return parameters; + } + + private MockServiceSelectorConfiguration extend(String key, String value) { + Map copy = new HashMap<>(parameters); + copy.put(key, value); + return new MockServiceSelectorConfiguration(copy); + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorProvider.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorProvider.java new file mode 100644 index 0000000000000..87f4bada8e7c6 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorProvider.java @@ -0,0 +1,26 @@ +package io.quarkus.micrometer.test; + +import jakarta.enterprise.context.ApplicationScoped; + +import org.mockito.Mockito; + +import io.smallrye.stork.api.LoadBalancer; +import io.smallrye.stork.api.ServiceDiscovery; +import io.smallrye.stork.api.config.LoadBalancerType; +import io.smallrye.stork.spi.LoadBalancerProvider; + +@LoadBalancerType("mock") +@ApplicationScoped +public class MockServiceSelectorProvider implements LoadBalancerProvider { + + private final LoadBalancer loadBalancer = Mockito.mock(LoadBalancer.class); + + @Override + public LoadBalancer createLoadBalancer(MockServiceSelectorConfiguration config, ServiceDiscovery serviceDiscovery) { + return loadBalancer; + } + + public LoadBalancer getLoadBalancer() { + return loadBalancer; + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorProviderLoader.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorProviderLoader.java new file mode 100644 index 0000000000000..c57d1991150ab --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/MockServiceSelectorProviderLoader.java @@ -0,0 +1,42 @@ +package io.quarkus.micrometer.test; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.spi.CDI; + +import io.smallrye.stork.api.LoadBalancer; +import io.smallrye.stork.api.ServiceDiscovery; +import io.smallrye.stork.api.config.ConfigWithType; + +/** + * LoadBalancerLoader for io.quarkus.it.rest.client.reactive.stork.MockLoadBalancerProvider + */ +@ApplicationScoped +public class MockServiceSelectorProviderLoader implements io.smallrye.stork.spi.internal.LoadBalancerLoader { + private final MockServiceSelectorProvider provider; + + public MockServiceSelectorProviderLoader() { + MockServiceSelectorProvider actual = null; + try { + actual = CDI.current().select(MockServiceSelectorProvider.class).get(); + } catch (Exception e) { + // Use direct instantiation + actual = new MockServiceSelectorProvider(); + } + this.provider = actual; + } + + @Override + public LoadBalancer createLoadBalancer(ConfigWithType config, ServiceDiscovery serviceDiscovery) { + io.quarkus.micrometer.test.MockServiceSelectorConfiguration typedConfig = new io.quarkus.micrometer.test.MockServiceSelectorConfiguration( + config.parameters()); + return provider.createLoadBalancer(typedConfig, serviceDiscovery); + } + + /** + * @return the type + */ + @Override + public String type() { + return "mock"; + } +} diff --git a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/Util.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/Util.java index fdf95698ebf8d..3760942d0a2fe 100644 --- a/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/Util.java +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/test/Util.java @@ -1,5 +1,7 @@ package io.quarkus.micrometer.test; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + import java.util.Arrays; import java.util.Collection; import java.util.List; @@ -8,7 +10,9 @@ import org.junit.jupiter.api.Assertions; +import io.micrometer.core.instrument.Meter; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; public class Util { private Util() { @@ -61,4 +65,11 @@ public static void waitForMeters(Collection collection, int count) throws Thread.sleep(3); } while (collection.size() < count && i++ < 10); } + + public static void assertTags(Tag tag, Meter... meters) { + for (Meter meter : meters) { + assertThat(meter.getId().getTags().contains(tag)); + } + } + } diff --git a/extensions/micrometer/runtime/pom.xml b/extensions/micrometer/runtime/pom.xml index c3da82799ccbc..c4f1679c24b20 100644 --- a/extensions/micrometer/runtime/pom.xml +++ b/extensions/micrometer/runtime/pom.xml @@ -113,6 +113,18 @@ true + + io.quarkus + quarkus-redis-client + true + + + + io.quarkus + quarkus-smallrye-stork + true + + io.quarkus.resteasy.reactive resteasy-reactive-client diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/stork/StorkObservationCollectorBean.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/stork/StorkObservationCollectorBean.java new file mode 100644 index 0000000000000..8657c0b3215f2 --- /dev/null +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/stork/StorkObservationCollectorBean.java @@ -0,0 +1,128 @@ +package io.quarkus.micrometer.runtime.binder.stork; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Typed; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.Timer; +import io.smallrye.stork.api.ServiceInstance; +import io.smallrye.stork.api.observability.ObservationCollector; +import io.smallrye.stork.api.observability.ObservationPoints; + +@ApplicationScoped +@Typed(ObservationCollector.class) +public class StorkObservationCollectorBean implements ObservationCollector { + + final MeterRegistry registry = Metrics.globalRegistry; + + private final EventCompletionHandler STORK_HANDLER = ev -> { + //TODO + }; + public static ObservationPoints.StorkResolutionEvent STORK_METRICS; + + @Override + public ObservationPoints.StorkResolutionEvent create(String serviceName, String serviceDiscoveryType, + String serviceSelectionType) { + STORK_METRICS = new ObservationPoints.StorkResolutionEvent(serviceName, serviceDiscoveryType, serviceSelectionType, + STORK_HANDLER) { + + private final Tags tags = Tags.of(Tag.of("service-name", getServiceName()));; + private final Counter instanceCounter = Counter.builder("stork.instances.count") + .description("The number of service instances discovered") + .tags(tags) + .register(registry);; + + private final Timer serviceDiscoveryTimer = Timer.builder("stork.service-discovery.duration") + .description("The duration of the discovery operation") + .tags(tags) + .register(registry); + + private final Timer serviceSelectionTimer = Timer.builder("stork.service-selection.duration") + .description("The duration of the selection operation ") + .tags(tags) + .register(registry); + + private final Timer overallTimer = Timer.builder("stork.overall.duration") + .description("The total duration of the Stork service discovery and selection operations") + .tags(tags) + .register(registry); + + private List serviceDiscoveryExceptions = new ArrayList<>(); + + private final Gauge serviceDiscoveryFailures = Gauge + .builder("stork.service-discovery.failures", serviceDiscoveryExceptions, List::size) + .description("The number of failures during service discovery").tags(tags) + .register(registry); + + private List serviceSelectionExceptions = new ArrayList<>(); + + private final Gauge serviceSelectionFailures = Gauge + .builder("stork.load-balancer.failures", serviceSelectionExceptions, List::size) + .description("The number of failures during service selection.").tags(tags) + .register(registry); + + @Override + public void onServiceDiscoverySuccess(List instances) { + endOfServiceDiscovery = System.nanoTime(); + serviceDiscoveryDone = true; + if (instances != null) { + instanceCounter.increment(); + } + serviceDiscoveryTimer.record(getServiceDiscoveryDuration().getNano(), TimeUnit.NANOSECONDS); + + } + + @Override + public int getDiscoveredInstancesCount() { + return (int) instanceCounter.count(); + } + + @Override + public void onServiceDiscoveryFailure(Throwable throwable) { + endOfServiceDiscovery = System.nanoTime(); + serviceDiscoveryDone = true; + failure = throwable; + serviceDiscoveryExceptions.add(throwable); + handler.complete(this); + serviceDiscoveryTimer.record(getServiceDiscoveryDuration().getNano(), TimeUnit.NANOSECONDS); + overallTimer.record(getServiceDiscoveryDuration().getNano(), TimeUnit.NANOSECONDS); + + } + + @Override + public void onServiceSelectionSuccess(long id) { + endOfServiceSelection = System.nanoTime(); + selectedInstanceId = id; + done = true; + handler.complete(this); + serviceSelectionTimer.record(getServiceSelectionDuration().getNano(), TimeUnit.NANOSECONDS); + overallTimer.record(getOverallDuration().getNano(), TimeUnit.NANOSECONDS); + + } + + @Override + public void onServiceSelectionFailure(Throwable throwable) { + endOfServiceSelection = System.nanoTime(); + if (failure != throwable) { //if SD fails we don't know count the error as a LB error + failure = throwable; + serviceSelectionExceptions.add(throwable); + } + handler.complete(this); + serviceSelectionTimer.record(getServiceSelectionDuration().getNano(), TimeUnit.NANOSECONDS); + overallTimer.record(getServiceSelectionDuration().getNano(), TimeUnit.NANOSECONDS); + } + + }; + return STORK_METRICS; + } + +} diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/MicrometerConfig.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/MicrometerConfig.java index 75042e33cf11d..7286227a22b23 100644 --- a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/MicrometerConfig.java +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/MicrometerConfig.java @@ -99,6 +99,7 @@ public static class BinderConfig { public KafkaConfigGroup kafka; public RedisConfigGroup redis; + public StorkConfigGroup stork; public GrpcServerConfigGroup grpcServer; diff --git a/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/StorkConfigGroup.java b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/StorkConfigGroup.java new file mode 100644 index 0000000000000..cca42c194fbb2 --- /dev/null +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/config/StorkConfigGroup.java @@ -0,0 +1,33 @@ +package io.quarkus.micrometer.runtime.config; + +import java.util.Optional; + +import io.quarkus.runtime.annotations.ConfigGroup; +import io.quarkus.runtime.annotations.ConfigItem; + +@ConfigGroup +public class StorkConfigGroup implements MicrometerConfig.CapabilityEnabled { + /** + * Stork metrics support. + *

+ * Support for Stork metrics will be enabled if Micrometer support is enabled, + * the Quarkus Stork extension is on the classpath + * and either this value is true, or this value is unset and + * {@code quarkus.micrometer.binder-enabled-default} is true. + */ + @ConfigItem + public Optional enabled; + + @Override + public Optional getEnabled() { + return enabled; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + + "{enabled=" + enabled + + '}'; + } + +} diff --git a/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/QuarkusStorkObservableInfrastructure.java b/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/QuarkusStorkObservableInfrastructure.java new file mode 100644 index 0000000000000..1030fab1b6696 --- /dev/null +++ b/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/QuarkusStorkObservableInfrastructure.java @@ -0,0 +1,22 @@ +package io.quarkus.stork; + +import jakarta.inject.Singleton; + +import io.smallrye.stork.api.observability.ObservationCollector; +import io.vertx.core.Vertx; + +@Singleton +public class QuarkusStorkObservableInfrastructure extends QuarkusStorkInfrastructure { + + private final ObservationCollector observationCollector; + + public QuarkusStorkObservableInfrastructure(Vertx vertx, ObservationCollector observationCollector) { + super(vertx); + this.observationCollector = observationCollector; + } + + @Override + public ObservationCollector getObservationCollector() { + return observationCollector; + } +} diff --git a/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/SmallRyeStorkRecorder.java b/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/SmallRyeStorkRecorder.java index dd82473fdac6c..4935a63c2cf29 100644 --- a/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/SmallRyeStorkRecorder.java +++ b/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/SmallRyeStorkRecorder.java @@ -2,11 +2,15 @@ import java.util.List; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.CDI; + import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.ShutdownContext; import io.quarkus.runtime.annotations.Recorder; import io.smallrye.stork.Stork; import io.smallrye.stork.api.config.ServiceConfig; +import io.smallrye.stork.api.observability.ObservationCollector; import io.vertx.core.Vertx; @Recorder @@ -15,7 +19,14 @@ public class SmallRyeStorkRecorder { public void initialize(ShutdownContext shutdown, RuntimeValue vertx, StorkConfiguration configuration) { List serviceConfigs = StorkConfigUtil.toStorkServiceConfig(configuration); StorkConfigProvider.init(serviceConfigs); - Stork.initialize(new QuarkusStorkInfrastructure(vertx.getValue())); + Instance instance = CDI.current().select(ObservationCollector.class); + if (instance.isResolvable()) { + Stork.initialize(new QuarkusStorkObservableInfrastructure(vertx.getValue(), instance.get())); + } else { + QuarkusStorkInfrastructure infrastructure = new QuarkusStorkInfrastructure(vertx.getValue()); + Stork.initialize(infrastructure); + } + shutdown.addShutdownTask(new Runnable() { @Override public void run() { diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 72498b8752c9e..3fbf2fee70c8a 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -67,7 +67,7 @@ 5.3.0 1.0.0.Final 2.15.2 - 2.3.1 + 2.4.0-SNAPSHOT 3.0.2 3.0.3 3.0.0 diff --git a/integration-tests/rest-client-reactive-stork/pom.xml b/integration-tests/rest-client-reactive-stork/pom.xml index 90f50aadf71f5..4a2387d3b9641 100644 --- a/integration-tests/rest-client-reactive-stork/pom.xml +++ b/integration-tests/rest-client-reactive-stork/pom.xml @@ -65,6 +65,10 @@ rest-assured test + + org.mockito + mockito-core + org.awaitility awaitility @@ -111,6 +115,29 @@ + + io.quarkus + quarkus-micrometer-registry-prometheus + + + + + + + + io.quarkus + quarkus-micrometer-registry-prometheus-deployment + ${project.version} + pom + test + + + * + * + + + + diff --git a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java index 5adb6924ee71b..6587f6d5c0279 100644 --- a/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java +++ b/integration-tests/rest-client-reactive-stork/src/test/java/io/quarkus/it/rest/reactive/stork/RestClientReactiveStorkTest.java @@ -7,15 +7,25 @@ import java.util.HashSet; import java.util.Set; +import java.util.concurrent.TimeUnit; +import jakarta.inject.Inject; + +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; import io.quarkus.arc.Arc; import io.quarkus.it.rest.client.reactive.stork.MyServiceDiscoveryProvider; +import io.quarkus.micrometer.runtime.binder.stork.StorkObservationCollectorBean; import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.DisabledOnIntegrationTest; import io.quarkus.test.junit.QuarkusTest; import io.restassured.response.Response; +import io.smallrye.stork.api.observability.ObservationPoints; import io.vertx.core.Vertx; @QuarkusTest @@ -23,6 +33,9 @@ @QuarkusTestResource(FastWiremockServer.class) public class RestClientReactiveStorkTest { + @Inject + MeterRegistry registry; + @Test @DisabledOnIntegrationTest void shouldUseQuarkusVertxInstance() { @@ -43,6 +56,10 @@ void shouldUseFasterService() { assertThat(responses).contains(FAST_RESPONSE, SLOW_RESPONSE); + //Check metrics + assertStorkMetrics(); + assertStorkMetricsOnMicrometerRegistry(); + responses.clear(); for (int i = 0; i < 3; i++) { @@ -54,4 +71,35 @@ void shouldUseFasterService() { // after hitting the slow endpoint, we should only use the fast one: assertThat(responses).containsOnly(FAST_RESPONSE, FAST_RESPONSE, FAST_RESPONSE); } + + private void assertStorkMetricsOnMicrometerRegistry() { + // Stork metrics exposed to Micrometer + Counter instanceCounter = registry.get("stork.instances.count").counter(); + Timer serviceDiscoveryDuration = registry.get("stork.service-discovery.duration").timer(); + Timer serviceSelectionDuration = registry.get("stork.service-selection.duration").timer(); + Timer overallDuration = registry.get("stork.overall.duration").timer(); + Gauge serviceDiscoveryFailures = registry.get("stork.service-discovery.failures").gauge(); + Gauge loadBalancerFailures = registry.get("stork.load-balancer.failures").gauge(); + + Assertions.assertThat(instanceCounter.count()).isEqualTo(2); + Assertions.assertThat(serviceDiscoveryFailures.value()).isEqualTo(0); + Assertions.assertThat(loadBalancerFailures.value()).isEqualTo(0); + Assertions.assertThat(serviceDiscoveryDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + Assertions.assertThat(serviceSelectionDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + Assertions.assertThat(overallDuration.totalTime(TimeUnit.NANOSECONDS)).isGreaterThan(0); + } + + private static void assertStorkMetrics() { + ObservationPoints.StorkResolutionEvent metrics = StorkObservationCollectorBean.STORK_METRICS; + assertThat(metrics.getDiscoveredInstancesCount()).isEqualTo(2); + assertThat(metrics.getSelectedInstanceId()).isNotNegative(); + assertThat(metrics.getServiceName()).isEqualTo("hello-service"); + assertThat(metrics.isDone()).isTrue(); + assertThat(metrics.failure()).isNull(); + assertThat(metrics.getOverallDuration()).isNotNull(); + assertThat(metrics.getServiceDiscoveryType()).isEqualTo("my"); + assertThat(metrics.getServiceSelectionType()).isEqualTo("least-response-time"); + assertThat(metrics.getServiceDiscoveryDuration()).isNotNull(); + assertThat(metrics.getServiceSelectionDuration()).isNotNull(); + } }