diff --git a/bom/application/pom.xml b/bom/application/pom.xml index a08f8bd25e445..c965086465fc7 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/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..8f53dfeb00135 --- /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 addRedisClientMetric() { + 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..5f3a30deabd30 --- /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.Service; + +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/StorkMetricsTest.java b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsTest.java new file mode 100644 index 0000000000000..faacd16a00139 --- /dev/null +++ b/extensions/micrometer/deployment/src/test/java/io/quarkus/micrometer/deployment/binder/StorkMetricsTest.java @@ -0,0 +1,22 @@ + +package io.quarkus.micrometer.deployment.binder; + +import jakarta.inject.Inject; + +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.MeterRegistry; +import io.quarkus.test.QuarkusUnitTest; + +@DisabledOnOs(OS.WINDOWS) +public class StorkMetricsTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest(); + + @Inject + MeterRegistry registry; + +} 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..de910185911c2 --- /dev/null +++ b/extensions/micrometer/runtime/src/main/java/io/quarkus/micrometer/runtime/binder/stork/StorkObservationCollectorBean.java @@ -0,0 +1,77 @@ +package io.quarkus.micrometer.runtime.binder.stork; + +import java.util.List; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Typed; + +import io.micrometer.core.instrument.Counter; +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("client-name", getServiceName()));; + private final Counter instanceCounter = Counter.builder("stork.instances.count") + .description("The number of service instances discovered") + .tags(tags) + .register(registry);; + private String name = serviceName; + + private volatile long endOfServiceDiscovery; + + private final Timer timer = Timer.builder("stork.service-discovery.duration") + .description("The duration of the operations (commands of batches") + .tags(tags) + .register(registry); + + @Override + public void onServiceDiscoverySuccess(List instances) { + this.endOfServiceDiscovery = System.nanoTime(); + if (instances != null) { + instanceCounter.increment(instances.size()); + } + + } + + @Override + public void onServiceDiscoveryFailure(Throwable throwable) { + // Noop + } + + @Override + public void onServiceSelectionSuccess(long id) { + // Noop + } + + @Override + public void onServiceSelectionFailure(Throwable throwable) { + // Noop + } + + }; + 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/SmallRyeStorkRecorder.java b/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/SmallRyeStorkRecorder.java index dd82473fdac6c..4f362a4a416d2 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,16 @@ 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.smallrye.stork.integration.ObservableStorkInfrastructure; import io.vertx.core.Vertx; @Recorder @@ -15,7 +20,13 @@ 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 ObservableStorkInfrastructure(instance.get())); + } else { + Stork.initialize(new QuarkusStorkInfrastructure(vertx.getValue())); + } + 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 db702dcaf0ecc..c5dde41c214be 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -68,7 +68,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..199f1fe2c302a 100644 --- a/integration-tests/rest-client-reactive-stork/pom.xml +++ b/integration-tests/rest-client-reactive-stork/pom.xml @@ -111,6 +111,19 @@ + + io.quarkus + quarkus-micrometer-registry-deployment + ${project.version} + pom + test + + + * + * + + + diff --git a/integration-tests/rest-client-reactive-stork/src/main/resources/application.properties b/integration-tests/rest-client-reactive-stork/src/main/resources/application.properties index a4730ffd76c7b..ef228150d0009 100644 --- a/integration-tests/rest-client-reactive-stork/src/main/resources/application.properties +++ b/integration-tests/rest-client-reactive-stork/src/main/resources/application.properties @@ -4,4 +4,6 @@ hello/mp-rest/url=stork://hello-service/hello # slow-service and fast-service come from Slow- and FastWiremockServer quarkus.stork.hello-service.service-discovery.address-list=${slow-service},${fast-service} quarkus.stork.hello-service.service-discovery.secure=true -quarkus.tls.trust-all=true \ No newline at end of file +quarkus.tls.trust-all=true + +quarkus.micrometer.binder.stork.enabled=true 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..657149e3d7fd7 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 @@ -12,10 +12,12 @@ 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 @@ -54,4 +56,29 @@ void shouldUseFasterService() { // after hitting the slow endpoint, we should only use the fast one: assertThat(responses).containsOnly(FAST_RESPONSE, FAST_RESPONSE, FAST_RESPONSE); } + + @Test + void shouldGetStorkMetrics() { + Set responses = new HashSet<>(); + + for (int i = 0; i < 2; i++) { + Response response = when().get("/client"); + response.then().statusCode(200); + responses.add(response.asString()); + } + + assertThat(responses).contains(FAST_RESPONSE, SLOW_RESPONSE); + + ObservationPoints.StorkResolutionEvent metrics = StorkObservationCollectorBean.STORK_METRICS; + assertThat(metrics.getDiscoveredInstancesCount()).isEqualTo(2); + 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(); + + } }