diff --git a/extensions/mongodb-client/deployment/pom.xml b/extensions/mongodb-client/deployment/pom.xml index 8bd99d1840900..36b104c57bf4b 100644 --- a/extensions/mongodb-client/deployment/pom.xml +++ b/extensions/mongodb-client/deployment/pom.xml @@ -63,7 +63,12 @@ io.quarkus - quarkus-smallrye-metrics-deployment + quarkus-micrometer-deployment + true + + + io.quarkus + quarkus-micrometer-registry-prometheus-deployment test @@ -86,6 +91,11 @@ awaitility test + + org.mockito + mockito-core + test + org.assertj assertj-core diff --git a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java index 6413be3720314..68003f57ea613 100644 --- a/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java +++ b/extensions/mongodb-client/deployment/src/main/java/io/quarkus/mongodb/deployment/MongoClientProcessor.java @@ -64,6 +64,7 @@ import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; import io.quarkus.mongodb.MongoClientName; +import io.quarkus.mongodb.metrics.MicrometerCommandListener; import io.quarkus.mongodb.reactive.ReactiveMongoClient; import io.quarkus.mongodb.runtime.MongoClientBeanUtil; import io.quarkus.mongodb.runtime.MongoClientCustomizer; @@ -124,6 +125,21 @@ AdditionalIndexedClassesBuildItem includeMongoCommandListener(MongoClientBuildTi return new AdditionalIndexedClassesBuildItem(); } + @BuildStep + void includeMongoCommandMetricListener( + BuildProducer additionalIndexedClasses, + MongoClientBuildTimeConfig buildTimeConfig, + Optional metricsCapability) { + if (!buildTimeConfig.metricsEnabled) { + return; + } + boolean withMicrometer = metricsCapability.map(cap -> cap.metricsSupported(MetricsFactory.MICROMETER)) + .orElse(false); + if (withMicrometer) { + additionalIndexedClasses.produce(new AdditionalIndexedClassesBuildItem(MicrometerCommandListener.class.getName())); + } + } + @BuildStep public void registerDnsProvider(BuildProducer nativeProducer) { nativeProducer.produce(new NativeImageResourceBuildItem("META-INF/services/" + DnsClientProvider.class.getName())); diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoLazyTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoLazyTest.java index c8c1d01716668..10c1cae82bf3a 100644 --- a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoLazyTest.java +++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoLazyTest.java @@ -4,58 +4,47 @@ import jakarta.inject.Inject; -import org.eclipse.microprofile.metrics.Metric; -import org.eclipse.microprofile.metrics.MetricID; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.eclipse.microprofile.metrics.Tag; -import org.eclipse.microprofile.metrics.annotation.RegistryType; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import com.mongodb.client.MongoClient; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; import io.quarkus.arc.Arc; -import io.quarkus.mongodb.metrics.ConnectionPoolGauge; import io.quarkus.mongodb.reactive.ReactiveMongoClient; import io.quarkus.test.QuarkusUnitTest; /** Variation of {@link io.quarkus.mongodb.MongoMetricsTest} to verify lazy client initialization. */ -public class MongoLazyTest extends MongoTestBase { +class MongoLazyTest extends MongoTestBase { @Inject - @RegistryType(type = MetricRegistry.Type.VENDOR) - MetricRegistry registry; + MeterRegistry meterRegistry; @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class)) + .withApplicationRoot(jar -> jar.addClasses(MongoTestBase.class)) .withConfigurationResource("application-metrics-mongo.properties"); @Test void testLazyClientCreation() { // Clients are created lazily, this metric should not be present yet - assertThat(getGaugeValueOrNull("mongodb.connection-pool.size", getTags())).isNull(); - assertThat(getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags())).isNull(); + assertThat(getMetric("mongodb.driver.pool.size")).isNull(); + assertThat(getMetric("mongodb.driver.pool.checkedout")).isNull(); + assertThat(getMetric("mongodb.driver.commands")).isNull(); // doing this here instead of in another method in order to avoid messing with the initialization stats assertThat(Arc.container().instance(MongoClient.class).get()).isNull(); assertThat(Arc.container().instance(ReactiveMongoClient.class).get()).isNull(); } - private Long getGaugeValueOrNull(String metricName, Tag[] tags) { - MetricID metricID = new MetricID(metricName, tags); - Metric metric = registry.getMetrics().get(metricID); - - if (metric == null) { - return null; - } - return ((ConnectionPoolGauge) metric).getValue(); + private Double getMetric(String name) { + Meter metric = meterRegistry.getMeters() + .stream() + .filter(mtr -> mtr.getId().getName().contains(name)) + .findFirst() + .orElse(null); + return metric == null ? null : metric.measure().iterator().next().getValue(); } - private Tag[] getTags() { - return new Tag[] { - new Tag("host", "127.0.0.1"), - new Tag("port", "27018"), - }; - } } diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoMetricsTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoMetricsTest.java index dab2253817afe..cc54cb1b61d8a 100644 --- a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoMetricsTest.java +++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/MongoMetricsTest.java @@ -1,38 +1,32 @@ package io.quarkus.mongodb; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import jakarta.inject.Inject; -import org.eclipse.microprofile.metrics.Metric; -import org.eclipse.microprofile.metrics.MetricID; -import org.eclipse.microprofile.metrics.MetricRegistry; -import org.eclipse.microprofile.metrics.Tag; -import org.eclipse.microprofile.metrics.annotation.RegistryType; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; import com.mongodb.client.MongoClient; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; import io.quarkus.arc.Arc; -import io.quarkus.mongodb.metrics.ConnectionPoolGauge; import io.quarkus.mongodb.reactive.ReactiveMongoClient; import io.quarkus.test.QuarkusUnitTest; -public class MongoMetricsTest extends MongoTestBase { +class MongoMetricsTest extends MongoTestBase { @Inject MongoClient client; @Inject - @RegistryType(type = MetricRegistry.Type.VENDOR) - MetricRegistry registry; + MeterRegistry meterRegistry; @RegisterExtension static final QuarkusUnitTest config = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar.addClasses(MongoTestBase.class)) + .withApplicationRoot(jar -> jar.addClasses(MongoTestBase.class)) .withConfigurationResource("application-metrics-mongo.properties"); @AfterEach @@ -45,38 +39,32 @@ void cleanup() { @Test void testMetricsInitialization() { // Clients are created lazily, this metric should not be present yet - assertThat(getGaugeValueOrNull("mongodb.connection-pool.size", getTags())).isNull(); - assertThat(getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags())).isNull(); + assertThat(getMetric("mongodb.driver.pool.size")).isNull(); + assertThat(getMetric("mongodb.driver.pool.checkedout")).isNull(); // Just need to execute something so that a connection is opened String name = client.listDatabaseNames().first(); - assertEquals(1L, getGaugeValueOrNull("mongodb.connection-pool.size", getTags())); - assertEquals(0L, getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags())); + assertThat(getMetric("mongodb.driver.pool.size")).isOne(); + assertThat(getMetric("mongodb.driver.commands")).isOne(); + assertThat(getMetric("mongodb.driver.pool.checkedout")).isZero(); client.close(); - assertEquals(0L, getGaugeValueOrNull("mongodb.connection-pool.size", getTags())); - assertEquals(0L, getGaugeValueOrNull("mongodb.connection-pool.checked-out-count", getTags())); + assertThat(getMetric("mongodb.driver.pool.size")).isNull(); + assertThat(getMetric("mongodb.driver.pool.checkedout")).isNull(); // doing this here instead of in another method in order to avoid messing with the initialization stats assertThat(Arc.container().instance(MongoClient.class).get()).isNotNull(); assertThat(Arc.container().instance(ReactiveMongoClient.class).get()).isNull(); } - private Long getGaugeValueOrNull(String metricName, Tag[] tags) { - MetricID metricID = new MetricID(metricName, tags); - Metric metric = registry.getMetrics().get(metricID); - - if (metric == null) { - return null; - } - return ((ConnectionPoolGauge) metric).getValue(); + private Double getMetric(String metricName) { + Meter metric = meterRegistry.getMeters() + .stream() + .filter(mtr -> mtr.getId().getName().contains(metricName)) + .findFirst() + .orElse(null); + return metric == null ? null : metric.measure().iterator().next().getValue(); } - private Tag[] getTags() { - return new Tag[] { - new Tag("host", "127.0.0.1"), - new Tag("port", "27018"), - }; - } } diff --git a/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/deployment/MongoClientProcessorTest.java b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/deployment/MongoClientProcessorTest.java new file mode 100644 index 0000000000000..ad437dc2dc882 --- /dev/null +++ b/extensions/mongodb-client/deployment/src/test/java/io/quarkus/mongodb/deployment/MongoClientProcessorTest.java @@ -0,0 +1,59 @@ +package io.quarkus.mongodb.deployment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.ArgumentCaptor; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.builditem.AdditionalIndexedClassesBuildItem; +import io.quarkus.deployment.metrics.MetricsCapabilityBuildItem; +import io.quarkus.runtime.metrics.MetricsFactory; + +class MongoClientProcessorTest { + private final MongoClientProcessor buildStep = new MongoClientProcessor(); + + @SuppressWarnings("unchecked") + @ParameterizedTest + @CsvSource({ + "true, true, true", // Metrics enabled and Micrometer supported + "true, false, false", // Metrics enabled but Micrometer not supported + "false, true, false", // Metrics disabled and Micrometer supported + "false, false, false" // Metrics disabled and Micrometer not supported + }) + void testIncludeMongoCommandMetricListener(boolean metricsEnabled, boolean micrometerSupported, boolean expectedResult) { + MongoClientBuildTimeConfig config = config(metricsEnabled); + Optional capability = capability(metricsEnabled, micrometerSupported); + + BuildProducer buildProducer = mock(BuildProducer.class); + buildStep.includeMongoCommandMetricListener(buildProducer, config, capability); + + if (expectedResult) { + var captor = ArgumentCaptor.forClass(AdditionalIndexedClassesBuildItem.class); + verify(buildProducer, times(1)).produce(captor.capture()); + assertThat(captor.getAllValues().get(0).getClassesToIndex()) + .containsExactly("io.quarkus.mongodb.metrics.MicrometerCommandListener"); + } else { + verify(buildProducer, never()).produce(any(AdditionalIndexedClassesBuildItem.class)); + } + } + + private static Optional capability(boolean metricsEnabled, boolean micrometerSupported) { + MetricsCapabilityBuildItem capability = metricsEnabled + ? new MetricsCapabilityBuildItem(factory -> MetricsFactory.MICROMETER.equals(factory) && micrometerSupported) + : null; + return Optional.ofNullable(capability); + } + + private static MongoClientBuildTimeConfig config(boolean metricsEnabled) { + MongoClientBuildTimeConfig buildTimeConfig = new MongoClientBuildTimeConfig(); + buildTimeConfig.metricsEnabled = metricsEnabled; + return buildTimeConfig; + } + +} diff --git a/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/metrics/MicrometerCommandListener.java b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/metrics/MicrometerCommandListener.java new file mode 100644 index 0000000000000..4ee642491a9c7 --- /dev/null +++ b/extensions/mongodb-client/runtime/src/main/java/io/quarkus/mongodb/metrics/MicrometerCommandListener.java @@ -0,0 +1,14 @@ +package io.quarkus.mongodb.metrics; + +import jakarta.inject.Inject; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.binder.mongodb.MongoMetricsCommandListener; + +public class MicrometerCommandListener extends MongoMetricsCommandListener { + @Inject + public MicrometerCommandListener(MeterRegistry registry) { + super(registry); + } + +} diff --git a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.java b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.java index d07a9733a4744..976dcc1e75243 100644 --- a/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.java +++ b/integration-tests/mongodb-panache/src/test/java/io/quarkus/it/mongodb/panache/MongodbPanacheResourceTest.java @@ -357,6 +357,7 @@ private void callPersonEndpoint(String endpoint) { .when().get("/q/metrics") .then() .statusCode(200) + .body(CoreMatchers.containsString("mongodb_driver_commands_seconds_max")) .body(CoreMatchers.containsString("mongodb_driver_pool_checkedout")) .body(CoreMatchers.containsString("mongodb_driver_pool_size")) .body(CoreMatchers.containsString("mongodb_driver_pool_waitqueuesize"));